From 1a6964c8274c50f0253db75f010d78ef0e739343 Mon Sep 17 00:00:00 2001 From: Git Development Community Date: Tue, 29 Sep 2009 16:47:03 -0700 Subject: [PATCH] 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 --- .gitattributes | 1 + .gitignore | 6 + LICENSE | 42 + README | 122 ++ SUBMITTING_PATCHES | 366 ++++ TODO | 49 + jgit-maven/.gitignore | 1 + jgit-maven/jgit/pom.xml | 226 +++ jgit.sh | 94 ++ make_jgit.sh | 179 ++ org.eclipse.jgit.pgm/.classpath | 8 + org.eclipse.jgit.pgm/.gitignore | 1 + org.eclipse.jgit.pgm/.project | 17 + .../org.eclipse.core.resources.prefs | 3 + .../.settings/org.eclipse.core.runtime.prefs | 3 + .../.settings/org.eclipse.jdt.core.prefs | 321 ++++ .../.settings/org.eclipse.jdt.ui.prefs | 9 + .../services/org.eclipse.jgit.pgm.TextBuiltin | 30 + .../jgit/pgm/AbstractFetchCommand.java | 137 ++ .../src/org/eclipse/jgit/pgm/Branch.java | 216 +++ .../src/org/eclipse/jgit/pgm/Clone.java | 187 +++ .../src/org/eclipse/jgit/pgm/Command.java | 78 + .../org/eclipse/jgit/pgm/CommandCatalog.java | 194 +++ .../src/org/eclipse/jgit/pgm/CommandRef.java | 164 ++ .../src/org/eclipse/jgit/pgm/Daemon.java | 125 ++ .../src/org/eclipse/jgit/pgm/Die.java | 65 + .../src/org/eclipse/jgit/pgm/DiffTree.java | 119 ++ .../src/org/eclipse/jgit/pgm/Fetch.java | 111 ++ .../src/org/eclipse/jgit/pgm/Glog.java | 132 ++ .../src/org/eclipse/jgit/pgm/IndexPack.java | 76 + .../src/org/eclipse/jgit/pgm/Init.java | 71 + .../src/org/eclipse/jgit/pgm/Log.java | 129 ++ .../src/org/eclipse/jgit/pgm/LsRemote.java | 86 + .../src/org/eclipse/jgit/pgm/LsTree.java | 85 + .../src/org/eclipse/jgit/pgm/Main.java | 184 ++ .../src/org/eclipse/jgit/pgm/MergeBase.java | 80 + .../src/org/eclipse/jgit/pgm/Push.java | 256 +++ .../src/org/eclipse/jgit/pgm/ReceivePack.java | 74 + .../src/org/eclipse/jgit/pgm/RevList.java | 81 + .../src/org/eclipse/jgit/pgm/RevParse.java | 74 + .../eclipse/jgit/pgm/RevWalkTextBuiltin.java | 208 +++ .../src/org/eclipse/jgit/pgm/Rm.java | 98 ++ .../src/org/eclipse/jgit/pgm/ShowRef.java | 68 + .../src/org/eclipse/jgit/pgm/Tag.java | 100 ++ .../src/org/eclipse/jgit/pgm/TextBuiltin.java | 241 +++ .../src/org/eclipse/jgit/pgm/UploadPack.java | 80 + .../src/org/eclipse/jgit/pgm/Version.java | 58 + .../eclipse/jgit/pgm/build/JarLinkUtil.java | 212 +++ .../eclipse/jgit/pgm/debug/MakeCacheTree.java | 74 + .../eclipse/jgit/pgm/debug/ReadDirCache.java | 60 + .../jgit/pgm/debug/RebuildCommitGraph.java | 313 ++++ .../eclipse/jgit/pgm/debug/ShowCacheTree.java | 76 + .../eclipse/jgit/pgm/debug/ShowCommands.java | 121 ++ .../eclipse/jgit/pgm/debug/ShowDirCache.java | 80 + .../eclipse/jgit/pgm/debug/WriteDirCache.java | 61 + .../pgm/opt/AbstractTreeIteratorHandler.java | 143 ++ .../eclipse/jgit/pgm/opt/CmdLineParser.java | 177 ++ .../eclipse/jgit/pgm/opt/ObjectIdHandler.java | 101 ++ .../jgit/pgm/opt/PathTreeFilterHandler.java | 108 ++ .../eclipse/jgit/pgm/opt/RefSpecHandler.java | 84 + .../jgit/pgm/opt/RevCommitHandler.java | 146 ++ .../eclipse/jgit/pgm/opt/RevTreeHandler.java | 114 ++ .../jgit/pgm/opt/SubcommandHandler.java | 100 ++ org.eclipse.jgit.test/.classpath | 11 + org.eclipse.jgit.test/.gitignore | 3 + org.eclipse.jgit.test/.project | 17 + .../org.eclipse.core.resources.prefs | 6 + .../.settings/org.eclipse.core.runtime.prefs | 3 + .../.settings/org.eclipse.jdt.core.prefs | 320 ++++ .../.settings/org.eclipse.jdt.ui.prefs | 10 + .../org/eclipse/jgit/lib/SpeedTestBase.java | 116 ++ .../jgit/lib/T0005_ShallowSpeedTest.java | 91 + .../eclipse/jgit/lib/T0006_DeepSpeedTest.java | 94 ++ .../jgit/patch/EGitPatchHistoryTest.java | 227 +++ ...t.core--All-External-Tests (Java 6).launch | 21 + ...lipse.jgit.core--All-External-Tests.launch | 20 + ...lipse.jgit.core--All-Tests (Java 6).launch | 21 + .../org.eclipse.jgit.core--All-Tests.launch | 20 + .../tst-rsrc/org/eclipse/jgit/diff/E.patch | 6 + .../org/eclipse/jgit/diff/E_PostImage | 1 + .../tst-rsrc/org/eclipse/jgit/diff/E_PreImage | 0 .../tst-rsrc/org/eclipse/jgit/diff/X.patch | 24 + .../org/eclipse/jgit/diff/X_PostImage | 28 + .../tst-rsrc/org/eclipse/jgit/diff/X_PreImage | 25 + .../tst-rsrc/org/eclipse/jgit/diff/Y.patch | 8 + .../org/eclipse/jgit/diff/Y_PostImage | 1 + .../tst-rsrc/org/eclipse/jgit/diff/Y_PreImage | 1 + .../tst-rsrc/org/eclipse/jgit/diff/Z.patch | 8 + .../org/eclipse/jgit/diff/Z_PostImage | 1 + .../tst-rsrc/org/eclipse/jgit/diff/Z_PreImage | 1 + .../org/eclipse/jgit/diff/testContext0.out | 18 + .../org/eclipse/jgit/diff/testContext1.out | 24 + .../org/eclipse/jgit/diff/testContext10.out | 37 + .../org/eclipse/jgit/diff/testContext100.out | 37 + .../org/eclipse/jgit/diff/testContext3.out | 30 + .../org/eclipse/jgit/diff/testContext5.out | 34 + .../org/eclipse/jgit/patch/.gitattributes | 1 + .../jgit/patch/testEditList_Types.patch | 24 + .../jgit/patch/testError_BodyTooLong.patch | 17 + .../jgit/patch/testError_CcTruncatedOld.patch | 24 + .../patch/testError_DisconnectedHunk.patch | 30 + .../patch/testError_GarbageBetweenFiles.patch | 33 + .../testError_GitBinaryNoForwardHunk.patch | 10 + .../jgit/patch/testError_TruncatedNew.patch | 15 + .../jgit/patch/testError_TruncatedOld.patch | 15 + .../jgit/patch/testGetText_BothISO88591.patch | 21 + .../jgit/patch/testGetText_Convert.patch | 21 + .../jgit/patch/testGetText_DiffCc.patch | 13 + .../jgit/patch/testGetText_NoBinary.patch | 4 + .../jgit/patch/testParse_AddNoNewline.patch | 20 + .../jgit/patch/testParse_CcDeleteFile.patch | 12 + .../jgit/patch/testParse_CcNewFile.patch | 14 + .../testParse_ConfigCaseInsensitive.patch | 67 + .../jgit/patch/testParse_FixNoNewline.patch | 20 + .../jgit/patch/testParse_GitBinaryDelta.patch | 21 + .../patch/testParse_GitBinaryLiteral.patch | 135 ++ .../jgit/patch/testParse_NoBinary.patch | 83 + .../jgit/patch/testParse_OneFileCc.patch | 27 + .../test/resources/all_packed_objects.txt | 96 ++ .../jgit/test/resources/create-second-pack | 166 ++ .../eclipse/jgit/test/resources/gitgit.index | Bin 0 -> 134799 bytes .../jgit/test/resources/gitgit.lsfiles | 1437 ++++++++++++++++ .../eclipse/jgit/test/resources/gitgit.lstree | 331 ++++ ...80af9c07ee18a87705ef50b0cc4cd20266cf12.idx | Bin 0 -> 1296 bytes ...0af9c07ee18a87705ef50b0cc4cd20266cf12.pack | Bin 0 -> 562 bytes ...be9032ac282b11fa9babdc2b2a93ca996c9c2f.idx | Bin 0 -> 1256 bytes ...9032ac282b11fa9babdc2b2a93ca996c9c2f.idxV2 | Bin 0 -> 1296 bytes ...e9032ac282b11fa9babdc2b2a93ca996c9c2f.pack | Bin 0 -> 7811 bytes ...6ff360fe3488adb20860ce3436a2d6373d2796.idx | Bin 0 -> 1324 bytes ...ff360fe3488adb20860ce3436a2d6373d2796.pack | Bin 0 -> 1265 bytes ...b5b411fe6dfa89cc2e6b89d2bd8e5de02b5745.idx | Bin 0 -> 1100 bytes ...5b411fe6dfa89cc2e6b89d2bd8e5de02b5745.pack | Bin 0 -> 164 bytes ...deda40019ae0e6e789088ea0f51f164f489d14.idx | Bin 0 -> 1240 bytes ...eda40019ae0e6e789088ea0f51f164f489d14.pack | Bin 0 -> 651 bytes ...2982f284bbabb6bdb59ee3fcc6eb0983e20371.idx | Bin 0 -> 2976 bytes ...82f284bbabb6bdb59ee3fcc6eb0983e20371.idxV2 | Bin 0 -> 2976 bytes ...982f284bbabb6bdb59ee3fcc6eb0983e20371.pack | Bin 0 -> 5901 bytes ...d07037cbcf13376308a0a995d1fa48f8f76aaa.idx | Bin 0 -> 1112 bytes ...07037cbcf13376308a0a995d1fa48f8f76aaa.pack | Bin 0 -> 1643 bytes .../eclipse/jgit/test/resources/pack-huge.idx | Bin 0 -> 2368 bytes .../eclipse/jgit/test/resources/packed-refs | 36 + .../jgit/diff/DiffFormatterReflowTest.java | 183 ++ .../org/eclipse/jgit/diff/EditListTest.java | 127 ++ .../tst/org/eclipse/jgit/diff/EditTest.java | 146 ++ .../org/eclipse/jgit/diff/RawTextTest.java | 100 ++ .../jgit/dircache/DirCacheBasicTest.java | 197 +++ .../dircache/DirCacheBuilderIteratorTest.java | 97 ++ .../jgit/dircache/DirCacheBuilderTest.java | 259 +++ .../DirCacheCGitCompatabilityTest.java | 219 +++ .../jgit/dircache/DirCacheFindTest.java | 92 + .../jgit/dircache/DirCacheIteratorTest.java | 275 +++ .../jgit/dircache/DirCacheLargePathTest.java | 112 ++ .../jgit/dircache/DirCacheTreeTest.java | 189 +++ .../jgit/fnmatch/FileNameMatcherTest.java | 764 +++++++++ .../jgit/lib/AbbreviatedObjectIdTest.java | 351 ++++ .../jgit/lib/ConcurrentRepackTest.java | 264 +++ .../jgit/lib/ConstantsEncodingTest.java | 95 ++ .../org/eclipse/jgit/lib/IndexDiffTest.java | 191 +++ .../eclipse/jgit/lib/IndexTreeWalkerTest.java | 152 ++ .../eclipse/jgit/lib/MockSystemReader.java | 97 ++ .../eclipse/jgit/lib/ObjectCheckerTest.java | 1331 +++++++++++++++ .../eclipse/jgit/lib/PackIndexTestCase.java | 168 ++ .../org/eclipse/jgit/lib/PackIndexV1Test.java | 82 + .../org/eclipse/jgit/lib/PackIndexV2Test.java | 93 ++ .../jgit/lib/PackReverseIndexTest.java | 121 ++ .../org/eclipse/jgit/lib/PackWriterTest.java | 533 ++++++ .../org/eclipse/jgit/lib/ReadTreeTest.java | 581 +++++++ .../tst/org/eclipse/jgit/lib/RefTest.java | 166 ++ .../org/eclipse/jgit/lib/RefUpdateTest.java | 768 +++++++++ .../eclipse/jgit/lib/ReflogReaderTest.java | 176 ++ .../eclipse/jgit/lib/RepositoryCacheTest.java | 132 ++ .../jgit/lib/RepositoryConfigTest.java | 259 +++ .../eclipse/jgit/lib/RepositoryTestCase.java | 347 ++++ .../org/eclipse/jgit/lib/T0001_ObjectId.java | 111 ++ .../eclipse/jgit/lib/T0001_PersonIdent.java | 111 ++ .../tst/org/eclipse/jgit/lib/T0002_Tree.java | 287 ++++ .../tst/org/eclipse/jgit/lib/T0003_Basic.java | 581 +++++++ .../eclipse/jgit/lib/T0004_PackReader.java | 114 ++ .../tst/org/eclipse/jgit/lib/T0007_Index.java | 455 +++++ .../eclipse/jgit/lib/T0008_testparserev.java | 142 ++ .../jgit/lib/TreeIteratorLeafOnlyTest.java | 129 ++ .../jgit/lib/TreeIteratorPostOrderTest.java | 147 ++ .../jgit/lib/TreeIteratorPreOrderTest.java | 147 ++ .../eclipse/jgit/lib/ValidRefNameTest.java | 177 ++ .../eclipse/jgit/lib/WindowCacheGetTest.java | 149 ++ .../jgit/lib/WindowCacheReconfigureTest.java | 125 ++ .../eclipse/jgit/lib/WorkDirCheckoutTest.java | 133 ++ .../org/eclipse/jgit/lib/XInputStream.java | 88 + .../org/eclipse/jgit/lib/empty.gitindex.dat | Bin 0 -> 32 bytes .../eclipse/jgit/lib/sorttest.gitindex.dat | Bin 0 -> 288 bytes .../eclipse/jgit/merge/CherryPickTest.java | 156 ++ .../eclipse/jgit/merge/SimpleMergeTest.java | 391 +++++ .../org/eclipse/jgit/patch/EditListTest.java | 101 ++ .../eclipse/jgit/patch/FileHeaderTest.java | 434 +++++ .../org/eclipse/jgit/patch/GetTextTest.java | 148 ++ .../eclipse/jgit/patch/PatchCcErrorTest.java | 103 ++ .../org/eclipse/jgit/patch/PatchCcTest.java | 206 +++ .../eclipse/jgit/patch/PatchErrorTest.java | 180 ++ .../tst/org/eclipse/jgit/patch/PatchTest.java | 358 ++++ .../jgit/revwalk/AlwaysEmptyRevQueueTest.java | 69 + .../jgit/revwalk/DateRevQueueTest.java | 124 ++ .../jgit/revwalk/FIFORevQueueTest.java | 87 + .../eclipse/jgit/revwalk/FooterLineTest.java | 323 ++++ .../jgit/revwalk/LIFORevQueueTest.java | 75 + .../eclipse/jgit/revwalk/ObjectWalkTest.java | 196 +++ .../jgit/revwalk/RevCommitParseTest.java | 321 ++++ .../eclipse/jgit/revwalk/RevFlagSetTest.java | 137 ++ .../eclipse/jgit/revwalk/RevObjectTest.java | 177 ++ .../jgit/revwalk/RevQueueTestCase.java | 91 + .../eclipse/jgit/revwalk/RevTagParseTest.java | 356 ++++ .../eclipse/jgit/revwalk/RevWalkCullTest.java | 100 ++ .../jgit/revwalk/RevWalkFilterTest.java | 297 ++++ .../jgit/revwalk/RevWalkMergeBaseTest.java | 139 ++ .../jgit/revwalk/RevWalkPathFilter1Test.java | 182 ++ .../revwalk/RevWalkPathFilter6012Test.java | 168 ++ .../eclipse/jgit/revwalk/RevWalkSortTest.java | 168 ++ .../eclipse/jgit/revwalk/RevWalkTestCase.java | 183 ++ .../jgit/transport/BundleWriterTest.java | 160 ++ .../eclipse/jgit/transport/IndexPackTest.java | 123 ++ .../eclipse/jgit/transport/LongMapTest.java | 138 ++ .../jgit/transport/OpenSshConfigTest.java | 228 +++ .../jgit/transport/PacketLineInTest.java | 268 +++ .../jgit/transport/PacketLineOutTest.java | 181 ++ .../jgit/transport/PushProcessTest.java | 418 +++++ .../eclipse/jgit/transport/RefSpecTest.java | 275 +++ .../jgit/transport/RemoteConfigTest.java | 432 +++++ .../transport/SideBandOutputStreamTest.java | 152 ++ .../eclipse/jgit/transport/TransportTest.java | 197 +++ .../eclipse/jgit/transport/URIishTest.java | 247 +++ .../treewalk/AbstractTreeIteratorTest.java | 169 ++ .../treewalk/CanonicalTreeParserTest.java | 353 ++++ .../jgit/treewalk/EmptyTreeIteratorTest.java | 112 ++ .../jgit/treewalk/FileTreeIteratorTest.java | 173 ++ .../treewalk/NameConflictTreeWalkTest.java | 211 +++ .../jgit/treewalk/PostOrderTreeWalkTest.java | 195 +++ .../jgit/treewalk/TreeWalkBasicDiffTest.java | 122 ++ .../filter/AlwaysCloneTreeFilter.java | 63 + .../treewalk/filter/NotTreeFilterTest.java | 91 + .../filter/PathSuffixFilterTestCase.java | 139 ++ .../jgit/treewalk/filter/TreeFilterTest.java | 80 + .../org/eclipse/jgit/util/IntListTest.java | 162 ++ .../org/eclipse/jgit/util/JGitTestUtil.java | 79 + .../tst/org/eclipse/jgit/util/NBTest.java | 334 ++++ .../util/QuotedStringBourneStyleTest.java | 117 ++ .../QuotedStringBourneUserPathStyleTest.java | 136 ++ .../util/QuotedStringGitPathStyleTest.java | 187 +++ .../jgit/util/RawParseUtils_HexParseTest.java | 164 ++ .../jgit/util/RawParseUtils_LineMapTest.java | 98 ++ .../jgit/util/RawParseUtils_MatchTest.java | 75 + .../eclipse/jgit/util/StringUtilsTest.java | 84 + .../jgit/util/TemporaryBufferTest.java | 380 +++++ .../tst/org/eclipse/jgit/util/TestRng.java | 67 + .../jgit/util/io/TimeoutInputStreamTest.java | 189 +++ .../jgit/util/io/TimeoutOutputStreamTest.java | 285 ++++ org.eclipse.jgit/.classpath | 7 + org.eclipse.jgit/.fbprefs | 125 ++ org.eclipse.jgit/.gitignore | 1 + org.eclipse.jgit/.project | 28 + .../org.eclipse.core.resources.prefs | 3 + .../.settings/org.eclipse.core.runtime.prefs | 3 + .../.settings/org.eclipse.jdt.core.prefs | 321 ++++ .../.settings/org.eclipse.jdt.ui.prefs | 9 + org.eclipse.jgit/META-INF/MANIFEST.MF | 21 + org.eclipse.jgit/build.properties | 5 + .../findBugs/FindBugsExcludeFilter.xml | 27 + org.eclipse.jgit/plugin.properties | 2 + .../eclipse/jgit/awtui/AWTPlotRenderer.java | 190 +++ .../eclipse/jgit/awtui/AwtAuthenticator.java | 197 +++ .../eclipse/jgit/awtui/CommitGraphPane.java | 256 +++ .../eclipse/jgit/awtui/SwingCommitList.java | 87 + .../org/eclipse/jgit/diff/DiffFormatter.java | 220 +++ .../src/org/eclipse/jgit/diff/Edit.java | 195 +++ .../src/org/eclipse/jgit/diff/EditList.java | 99 ++ .../src/org/eclipse/jgit/diff/RawText.java | 190 +++ .../src/org/eclipse/jgit/diff/Sequence.java | 84 + .../jgit/dircache/BaseDirCacheEditor.java | 201 +++ .../org/eclipse/jgit/dircache/DirCache.java | 756 +++++++++ .../jgit/dircache/DirCacheBuildIterator.java | 133 ++ .../jgit/dircache/DirCacheBuilder.java | 252 +++ .../eclipse/jgit/dircache/DirCacheEditor.java | 272 +++ .../eclipse/jgit/dircache/DirCacheEntry.java | 502 ++++++ .../jgit/dircache/DirCacheIterator.java | 239 +++ .../eclipse/jgit/dircache/DirCacheTree.java | 575 +++++++ .../errors/CheckoutConflictException.java | 82 + .../jgit/errors/CompoundException.java | 87 + .../jgit/errors/ConfigInvalidException.java | 71 + .../jgit/errors/CorruptObjectException.java | 91 + .../jgit/errors/EntryExistsException.java | 64 + .../errors/GitlinksNotSupportedException.java | 65 + .../errors/IncorrectObjectTypeException.java | 86 + .../jgit/errors/InvalidObjectIdException.java | 76 + .../jgit/errors/InvalidPatternException.java | 75 + .../MissingBundlePrerequisiteException.java | 79 + .../jgit/errors/MissingObjectException.java | 81 + .../errors/NoClosingBracketException.java | 79 + .../errors/NoRemoteRepositoryException.java | 65 + .../jgit/errors/NotSupportedException.java | 77 + .../jgit/errors/ObjectWritingException.java | 75 + .../jgit/errors/PackInvalidException.java | 62 + .../jgit/errors/PackMismatchException.java | 61 + .../jgit/errors/PackProtocolException.java | 107 ++ .../errors/RepositoryNotFoundException.java | 71 + .../eclipse/jgit/errors/RevWalkException.java | 70 + .../jgit/errors/RevisionSyntaxException.java | 85 + .../jgit/errors/StopWalkException.java | 61 + .../errors/SymlinksNotSupportedException.java | 64 + .../jgit/errors/TransportException.java | 109 ++ .../jgit/errors/UnmergedPathException.java | 73 + .../eclipse/jgit/fnmatch/AbstractHead.java | 81 + .../eclipse/jgit/fnmatch/CharacterHead.java | 60 + .../eclipse/jgit/fnmatch/FileNameMatcher.java | 382 +++++ .../org/eclipse/jgit/fnmatch/GroupHead.java | 227 +++ .../src/org/eclipse/jgit/fnmatch/Head.java | 57 + .../org/eclipse/jgit/fnmatch/LastHead.java | 63 + .../jgit/fnmatch/RestrictedWildCardHead.java | 59 + .../eclipse/jgit/fnmatch/WildCardHead.java | 56 + .../eclipse/jgit/lib/AbbreviatedObjectId.java | 260 +++ .../jgit/lib/AbstractIndexTreeVisitor.java | 79 + .../jgit/lib/AlternateRepositoryDatabase.java | 133 ++ .../src/org/eclipse/jgit/lib/AnyObjectId.java | 492 ++++++ .../src/org/eclipse/jgit/lib/BinaryDelta.java | 141 ++ .../org/eclipse/jgit/lib/BlobBasedConfig.java | 129 ++ .../org/eclipse/jgit/lib/ByteArrayWindow.java | 97 ++ .../eclipse/jgit/lib/ByteBufferWindow.java | 110 ++ .../src/org/eclipse/jgit/lib/ByteWindow.java | 185 +++ .../src/org/eclipse/jgit/lib/Commit.java | 380 +++++ .../src/org/eclipse/jgit/lib/Config.java | 1154 +++++++++++++ .../src/org/eclipse/jgit/lib/Constants.java | 463 ++++++ .../src/org/eclipse/jgit/lib/CoreConfig.java | 87 + .../jgit/lib/DeltaOfsPackedObjectLoader.java | 82 + .../jgit/lib/DeltaPackedObjectLoader.java | 116 ++ .../jgit/lib/DeltaRefPackedObjectLoader.java | 81 + .../org/eclipse/jgit/lib/FileBasedConfig.java | 149 ++ .../src/org/eclipse/jgit/lib/FileMode.java | 254 +++ .../org/eclipse/jgit/lib/FileTreeEntry.java | 117 ++ .../org/eclipse/jgit/lib/ForceModified.java | 75 + .../src/org/eclipse/jgit/lib/GitIndex.java | 976 +++++++++++ .../eclipse/jgit/lib/GitlinkTreeEntry.java | 92 + .../eclipse/jgit/lib/IndexChangedEvent.java | 61 + .../src/org/eclipse/jgit/lib/IndexDiff.java | 166 ++ .../eclipse/jgit/lib/IndexTreeVisitor.java | 103 ++ .../org/eclipse/jgit/lib/IndexTreeWalker.java | 248 +++ .../org/eclipse/jgit/lib/InflaterCache.java | 109 ++ .../src/org/eclipse/jgit/lib/LockFile.java | 411 +++++ .../org/eclipse/jgit/lib/MutableObjectId.java | 186 +++ .../eclipse/jgit/lib/NullProgressMonitor.java | 78 + .../org/eclipse/jgit/lib/ObjectChecker.java | 365 ++++ .../org/eclipse/jgit/lib/ObjectDatabase.java | 383 +++++ .../org/eclipse/jgit/lib/ObjectDirectory.java | 517 ++++++ .../src/org/eclipse/jgit/lib/ObjectId.java | 273 +++ .../eclipse/jgit/lib/ObjectIdSubclassMap.java | 183 ++ .../org/eclipse/jgit/lib/ObjectLoader.java | 105 ++ .../org/eclipse/jgit/lib/ObjectWriter.java | 412 +++++ .../src/org/eclipse/jgit/lib/OffsetCache.java | 539 ++++++ .../src/org/eclipse/jgit/lib/PackFile.java | 515 ++++++ .../src/org/eclipse/jgit/lib/PackIndex.java | 316 ++++ .../src/org/eclipse/jgit/lib/PackIndexV1.java | 201 +++ .../src/org/eclipse/jgit/lib/PackIndexV2.java | 277 ++++ .../org/eclipse/jgit/lib/PackIndexWriter.java | 273 +++ .../eclipse/jgit/lib/PackIndexWriterV1.java | 84 + .../eclipse/jgit/lib/PackIndexWriterV2.java | 107 ++ .../src/org/eclipse/jgit/lib/PackLock.java | 90 + .../eclipse/jgit/lib/PackOutputStream.java | 107 ++ .../eclipse/jgit/lib/PackReverseIndex.java | 191 +++ .../src/org/eclipse/jgit/lib/PackWriter.java | 1045 ++++++++++++ .../eclipse/jgit/lib/PackedObjectLoader.java | 193 +++ .../src/org/eclipse/jgit/lib/PersonIdent.java | 339 ++++ .../org/eclipse/jgit/lib/ProgressMonitor.java | 97 ++ .../src/org/eclipse/jgit/lib/Ref.java | 285 ++++ .../org/eclipse/jgit/lib/RefComparator.java | 79 + .../src/org/eclipse/jgit/lib/RefDatabase.java | 522 ++++++ .../org/eclipse/jgit/lib/RefLogWriter.java | 155 ++ .../src/org/eclipse/jgit/lib/RefRename.java | 175 ++ .../src/org/eclipse/jgit/lib/RefUpdate.java | 642 +++++++ .../src/org/eclipse/jgit/lib/RefWriter.java | 171 ++ .../org/eclipse/jgit/lib/ReflogReader.java | 183 ++ .../eclipse/jgit/lib/RefsChangedEvent.java | 61 + .../src/org/eclipse/jgit/lib/Repository.java | 1176 +++++++++++++ .../eclipse/jgit/lib/RepositoryAdapter.java | 60 + .../org/eclipse/jgit/lib/RepositoryCache.java | 390 +++++ .../jgit/lib/RepositoryChangedEvent.java | 70 + .../eclipse/jgit/lib/RepositoryConfig.java | 135 ++ .../eclipse/jgit/lib/RepositoryListener.java | 69 + .../org/eclipse/jgit/lib/RepositoryState.java | 161 ++ .../eclipse/jgit/lib/SymlinkTreeEntry.java | 90 + .../src/org/eclipse/jgit/lib/Tag.java | 302 ++++ .../eclipse/jgit/lib/TextProgressMonitor.java | 142 ++ .../org/eclipse/jgit/lib/TransferConfig.java | 71 + .../src/org/eclipse/jgit/lib/Tree.java | 608 +++++++ .../src/org/eclipse/jgit/lib/TreeEntry.java | 301 ++++ .../org/eclipse/jgit/lib/TreeIterator.java | 209 +++ .../src/org/eclipse/jgit/lib/TreeVisitor.java | 95 ++ .../lib/TreeVisitorWithCurrentDirectory.java | 78 + .../src/org/eclipse/jgit/lib/Treeish.java | 63 + .../eclipse/jgit/lib/UnpackedObjectCache.java | 195 +++ .../jgit/lib/UnpackedObjectLoader.java | 212 +++ .../src/org/eclipse/jgit/lib/UserConfig.java | 155 ++ .../jgit/lib/WholePackedObjectLoader.java | 107 ++ .../src/org/eclipse/jgit/lib/WindowCache.java | 264 +++ .../eclipse/jgit/lib/WindowCacheConfig.java | 176 ++ .../org/eclipse/jgit/lib/WindowCursor.java | 180 ++ .../org/eclipse/jgit/lib/WorkDirCheckout.java | 406 +++++ .../src/org/eclipse/jgit/lib/WriteTree.java | 95 ++ .../org/eclipse/jgit/merge/MergeStrategy.java | 141 ++ .../src/org/eclipse/jgit/merge/Merger.java | 235 +++ .../eclipse/jgit/merge/StrategyOneSided.java | 105 ++ .../merge/StrategySimpleTwoWayInCore.java | 193 +++ .../jgit/merge/ThreeWayMergeStrategy.java | 52 + .../eclipse/jgit/merge/ThreeWayMerger.java | 136 ++ .../org/eclipse/jgit/patch/BinaryHunk.java | 149 ++ .../jgit/patch/CombinedFileHeader.java | 246 +++ .../jgit/patch/CombinedHunkHeader.java | 324 ++++ .../org/eclipse/jgit/patch/FileHeader.java | 714 ++++++++ .../org/eclipse/jgit/patch/FormatError.java | 115 ++ .../org/eclipse/jgit/patch/HunkHeader.java | 383 +++++ .../src/org/eclipse/jgit/patch/Patch.java | 382 +++++ .../jgit/revplot/AbstractPlotRenderer.java | 273 +++ .../org/eclipse/jgit/revplot/PlotCommit.java | 166 ++ .../eclipse/jgit/revplot/PlotCommitList.java | 196 +++ .../org/eclipse/jgit/revplot/PlotLane.java | 72 + .../org/eclipse/jgit/revplot/PlotWalk.java | 139 ++ .../jgit/revwalk/AbstractRevQueue.java | 156 ++ .../eclipse/jgit/revwalk/BlockObjQueue.java | 144 ++ .../eclipse/jgit/revwalk/BlockRevQueue.java | 169 ++ .../jgit/revwalk/BoundaryGenerator.java | 134 ++ .../eclipse/jgit/revwalk/DateRevQueue.java | 161 ++ .../eclipse/jgit/revwalk/DelayRevQueue.java | 99 ++ .../eclipse/jgit/revwalk/EndGenerator.java | 62 + .../eclipse/jgit/revwalk/FIFORevQueue.java | 165 ++ .../revwalk/FixUninterestingGenerator.java | 84 + .../org/eclipse/jgit/revwalk/FooterKey.java | 83 + .../org/eclipse/jgit/revwalk/FooterLine.java | 151 ++ .../org/eclipse/jgit/revwalk/Generator.java | 106 ++ .../eclipse/jgit/revwalk/LIFORevQueue.java | 121 ++ .../jgit/revwalk/MergeBaseGenerator.java | 224 +++ .../org/eclipse/jgit/revwalk/ObjectWalk.java | 432 +++++ .../jgit/revwalk/PendingGenerator.java | 182 ++ .../src/org/eclipse/jgit/revwalk/RevBlob.java | 67 + .../org/eclipse/jgit/revwalk/RevCommit.java | 529 ++++++ .../eclipse/jgit/revwalk/RevCommitList.java | 359 ++++ .../src/org/eclipse/jgit/revwalk/RevFlag.java | 101 ++ .../org/eclipse/jgit/revwalk/RevFlagSet.java | 154 ++ .../org/eclipse/jgit/revwalk/RevObject.java | 218 +++ .../eclipse/jgit/revwalk/RevObjectList.java | 125 ++ .../src/org/eclipse/jgit/revwalk/RevSort.java | 90 + .../src/org/eclipse/jgit/revwalk/RevTag.java | 221 +++ .../src/org/eclipse/jgit/revwalk/RevTree.java | 67 + .../src/org/eclipse/jgit/revwalk/RevWalk.java | 1085 ++++++++++++ .../jgit/revwalk/RewriteGenerator.java | 188 +++ .../jgit/revwalk/RewriteTreeFilter.java | 216 +++ .../eclipse/jgit/revwalk/StartGenerator.java | 173 ++ .../jgit/revwalk/TopoSortGenerator.java | 129 ++ .../jgit/revwalk/filter/AndRevFilter.java | 184 ++ .../jgit/revwalk/filter/AuthorRevFilter.java | 116 ++ .../revwalk/filter/CommitTimeRevFilter.java | 167 ++ .../revwalk/filter/CommitterRevFilter.java | 116 ++ .../jgit/revwalk/filter/MessageRevFilter.java | 116 ++ .../jgit/revwalk/filter/NotRevFilter.java | 93 ++ .../jgit/revwalk/filter/OrRevFilter.java | 182 ++ .../revwalk/filter/PatternMatchRevFilter.java | 146 ++ .../jgit/revwalk/filter/RevFilter.java | 232 +++ .../jgit/revwalk/filter/RevFlagFilter.java | 161 ++ .../revwalk/filter/SubStringRevFilter.java | 125 ++ .../org/eclipse/jgit/transport/AmazonS3.java | 795 +++++++++ .../jgit/transport/BaseConnection.java | 109 ++ .../jgit/transport/BaseFetchConnection.java | 100 ++ .../jgit/transport/BasePackConnection.java | 287 ++++ .../transport/BasePackFetchConnection.java | 538 ++++++ .../transport/BasePackPushConnection.java | 296 ++++ .../jgit/transport/BundleFetchConnection.java | 273 +++ .../eclipse/jgit/transport/BundleWriter.java | 203 +++ .../eclipse/jgit/transport/Connection.java | 110 ++ .../org/eclipse/jgit/transport/Daemon.java | 391 +++++ .../eclipse/jgit/transport/DaemonClient.java | 115 ++ .../eclipse/jgit/transport/DaemonService.java | 149 ++ .../transport/DefaultSshSessionFactory.java | 175 ++ .../jgit/transport/FetchConnection.java | 177 ++ .../jgit/transport/FetchHeadRecord.java | 95 ++ .../eclipse/jgit/transport/FetchProcess.java | 451 +++++ .../eclipse/jgit/transport/FetchResult.java | 69 + .../eclipse/jgit/transport/HttpTransport.java | 69 + .../org/eclipse/jgit/transport/IndexPack.java | 1107 +++++++++++++ .../org/eclipse/jgit/transport/LongMap.java | 158 ++ .../eclipse/jgit/transport/OpenSshConfig.java | 400 +++++ .../jgit/transport/OperationResult.java | 139 ++ .../eclipse/jgit/transport/PackTransport.java | 61 + .../jgit/transport/PackedObjectInfo.java | 115 ++ .../eclipse/jgit/transport/PacketLineIn.java | 139 ++ .../eclipse/jgit/transport/PacketLineOut.java | 103 ++ .../jgit/transport/PostReceiveHook.java | 83 + .../jgit/transport/PreReceiveHook.java | 100 ++ .../jgit/transport/PushConnection.java | 114 ++ .../eclipse/jgit/transport/PushProcess.java | 242 +++ .../eclipse/jgit/transport/PushResult.java | 92 + .../jgit/transport/ReceiveCommand.java | 229 +++ .../eclipse/jgit/transport/ReceivePack.java | 913 ++++++++++ .../eclipse/jgit/transport/RefAdvertiser.java | 195 +++ .../org/eclipse/jgit/transport/RefSpec.java | 463 ++++++ .../eclipse/jgit/transport/RemoteConfig.java | 508 ++++++ .../jgit/transport/RemoteRefUpdate.java | 360 ++++ .../jgit/transport/SideBandInputStream.java | 235 +++ .../jgit/transport/SideBandOutputStream.java | 99 ++ .../transport/SideBandProgressMonitor.java | 156 ++ .../transport/SshConfigSessionFactory.java | 236 +++ .../jgit/transport/SshSessionFactory.java | 130 ++ .../eclipse/jgit/transport/SshTransport.java | 153 ++ .../org/eclipse/jgit/transport/TagOpt.java | 110 ++ .../eclipse/jgit/transport/TcpTransport.java | 69 + .../jgit/transport/TrackingRefUpdate.java | 141 ++ .../org/eclipse/jgit/transport/Transport.java | 932 +++++++++++ .../jgit/transport/TransportAmazonS3.java | 336 ++++ .../jgit/transport/TransportBundle.java | 62 + .../jgit/transport/TransportBundleFile.java | 103 ++ .../jgit/transport/TransportBundleStream.java | 121 ++ .../jgit/transport/TransportGitAnon.java | 197 +++ .../jgit/transport/TransportGitSsh.java | 392 +++++ .../eclipse/jgit/transport/TransportHttp.java | 282 ++++ .../jgit/transport/TransportLocal.java | 407 +++++ .../eclipse/jgit/transport/TransportSftp.java | 432 +++++ .../org/eclipse/jgit/transport/URIish.java | 366 ++++ .../eclipse/jgit/transport/UploadPack.java | 486 ++++++ .../jgit/transport/WalkEncryption.java | 194 +++ .../jgit/transport/WalkFetchConnection.java | 878 ++++++++++ .../jgit/transport/WalkPushConnection.java | 382 +++++ .../transport/WalkRemoteObjectDatabase.java | 513 ++++++ .../eclipse/jgit/transport/WalkTransport.java | 63 + .../jgit/treewalk/AbstractTreeIterator.java | 595 +++++++ .../jgit/treewalk/CanonicalTreeParser.java | 368 ++++ .../jgit/treewalk/EmptyTreeIterator.java | 135 ++ .../jgit/treewalk/FileTreeIterator.java | 175 ++ .../jgit/treewalk/NameConflictTreeWalk.java | 312 ++++ .../org/eclipse/jgit/treewalk/TreeWalk.java | 921 ++++++++++ .../jgit/treewalk/WorkingTreeIterator.java | 457 +++++ .../jgit/treewalk/filter/AndTreeFilter.java | 196 +++ .../jgit/treewalk/filter/NotTreeFilter.java | 99 ++ .../jgit/treewalk/filter/OrTreeFilter.java | 194 +++ .../jgit/treewalk/filter/PathFilter.java | 113 ++ .../jgit/treewalk/filter/PathFilterGroup.java | 206 +++ .../treewalk/filter/PathSuffixFilter.java | 103 ++ .../jgit/treewalk/filter/TreeFilter.java | 225 +++ .../src/org/eclipse/jgit/util/Base64.java | 1475 +++++++++++++++++ .../src/org/eclipse/jgit/util/FS.java | 184 ++ .../org/eclipse/jgit/util/FS_POSIX_Java5.java | 60 + .../org/eclipse/jgit/util/FS_POSIX_Java6.java | 107 ++ .../src/org/eclipse/jgit/util/FS_Win32.java | 74 + .../eclipse/jgit/util/FS_Win32_Cygwin.java | 121 ++ .../org/eclipse/jgit/util/HttpSupport.java | 171 ++ .../src/org/eclipse/jgit/util/IntList.java | 134 ++ .../org/eclipse/jgit/util/MutableInteger.java | 50 + .../src/org/eclipse/jgit/util/NB.java | 368 ++++ .../org/eclipse/jgit/util/QuotedString.java | 368 ++++ .../eclipse/jgit/util/RawCharSequence.java | 97 ++ .../org/eclipse/jgit/util/RawParseUtils.java | 1016 ++++++++++++ .../jgit/util/RawSubStringPattern.java | 137 ++ .../org/eclipse/jgit/util/StringUtils.java | 121 ++ .../org/eclipse/jgit/util/SystemReader.java | 153 ++ .../eclipse/jgit/util/TemporaryBuffer.java | 334 ++++ .../eclipse/jgit/util/io/InterruptTimer.java | 222 +++ .../jgit/util/io/TimeoutInputStream.java | 139 ++ .../jgit/util/io/TimeoutOutputStream.java | 152 ++ tag_jgit.sh | 79 + 561 files changed, 103222 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README create mode 100644 SUBMITTING_PATCHES create mode 100644 TODO create mode 100644 jgit-maven/.gitignore create mode 100644 jgit-maven/jgit/pom.xml create mode 100644 jgit.sh create mode 100644 make_jgit.sh create mode 100644 org.eclipse.jgit.pgm/.classpath create mode 100644 org.eclipse.jgit.pgm/.gitignore create mode 100644 org.eclipse.jgit.pgm/.project create mode 100644 org.eclipse.jgit.pgm/.settings/org.eclipse.core.resources.prefs create mode 100644 org.eclipse.jgit.pgm/.settings/org.eclipse.core.runtime.prefs create mode 100644 org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs create mode 100644 org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.ui.prefs create mode 100644 org.eclipse.jgit.pgm/src/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Command.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandCatalog.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandRef.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Die.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Fetch.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/IndexPack.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeBase.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ReceivePack.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevList.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ShowRef.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/UploadPack.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Version.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/build/JarLinkUtil.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/MakeCacheTree.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadDirCache.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCacheTree.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCommands.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteDirCache.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/PathTreeFilterHandler.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RefSpecHandler.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java create mode 100644 org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java create mode 100644 org.eclipse.jgit.test/.classpath create mode 100644 org.eclipse.jgit.test/.gitignore create mode 100644 org.eclipse.jgit.test/.project create mode 100644 org.eclipse.jgit.test/.settings/org.eclipse.core.resources.prefs create mode 100644 org.eclipse.jgit.test/.settings/org.eclipse.core.runtime.prefs create mode 100644 org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs create mode 100644 org.eclipse.jgit.test/.settings/org.eclipse.jdt.ui.prefs create mode 100644 org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/SpeedTestBase.java create mode 100644 org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/T0005_ShallowSpeedTest.java create mode 100644 org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/T0006_DeepSpeedTest.java create mode 100644 org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java create mode 100644 org.eclipse.jgit.test/org.eclipse.jgit.core--All-External-Tests (Java 6).launch create mode 100644 org.eclipse.jgit.test/org.eclipse.jgit.core--All-External-Tests.launch create mode 100644 org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests (Java 6).launch create mode 100644 org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests.launch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/E.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/E_PostImage create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/E_PreImage create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/X.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/X_PostImage create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/X_PreImage create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Y.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Y_PostImage create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Y_PreImage create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Z.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Z_PostImage create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Z_PreImage create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext0.out create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext1.out create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext10.out create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext100.out create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext3.out create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext5.out create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/.gitattributes create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testEditList_Types.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_BodyTooLong.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_CcTruncatedOld.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_DisconnectedHunk.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_GarbageBetweenFiles.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_GitBinaryNoForwardHunk.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_TruncatedNew.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_TruncatedOld.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testGetText_BothISO88591.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testGetText_Convert.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testGetText_DiffCc.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testGetText_NoBinary.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_AddNoNewline.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_CcDeleteFile.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_CcNewFile.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_ConfigCaseInsensitive.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_FixNoNewline.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_GitBinaryDelta.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_GitBinaryLiteral.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_NoBinary.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_OneFileCc.patch create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/all_packed_objects.txt create mode 100755 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/create-second-pack create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.index create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.lsfiles create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.lstree create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-3280af9c07ee18a87705ef50b0cc4cd20266cf12.idx create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-3280af9c07ee18a87705ef50b0cc4cd20266cf12.pack create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idx create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idxV2 create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.pack create mode 100755 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-546ff360fe3488adb20860ce3436a2d6373d2796.idx create mode 100755 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-546ff360fe3488adb20860ce3436a2d6373d2796.pack create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-9fb5b411fe6dfa89cc2e6b89d2bd8e5de02b5745.idx create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-9fb5b411fe6dfa89cc2e6b89d2bd8e5de02b5745.pack create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-cbdeda40019ae0e6e789088ea0f51f164f489d14.idx create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-cbdeda40019ae0e6e789088ea0f51f164f489d14.pack create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.idx create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.idxV2 create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.pack create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-e6d07037cbcf13376308a0a995d1fa48f8f76aaa.idx create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-e6d07037cbcf13376308a0a995d1fa48f8f76aaa.pack create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-huge.idx create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/packed-refs create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterReflowTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditListTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheFindTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheLargePathTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheTreeTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/fnmatch/FileNameMatcherTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConstantsEncodingTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexTreeWalkerTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MockSystemReader.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexTestCase.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV1Test.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV2Test.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackReverseIndexTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogReaderTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryConfigTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_ObjectId.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdent.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_Tree.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0007_Index.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0008_testparserev.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/TreeIteratorLeafOnlyTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/TreeIteratorPostOrderTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/TreeIteratorPreOrderTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ValidRefNameTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheGetTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheReconfigureTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WorkDirCheckoutTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/XInputStream.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/empty.gitindex.dat create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/sorttest.gitindex.dat create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/EditListTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/FileHeaderTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/GetTextTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcErrorTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchErrorTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/AlwaysEmptyRevQueueTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/DateRevQueueTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FIFORevQueueTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FooterLineTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/LIFORevQueueTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevFlagSetTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevObjectTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevQueueTestCase.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCullTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter1Test.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter6012Test.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkSortTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/IndexPackTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineInTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineOutTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/EmptyTreeIteratorTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/NameConflictTreeWalkTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/AlwaysCloneTreeFilter.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/NotTreeFilterTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTestCase.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/TreeFilterTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/util/JGitTestUtil.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringBourneStyleTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringBourneUserPathStyleTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringGitPathStyleTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_HexParseTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_MatchTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/util/TemporaryBufferTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/util/TestRng.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutInputStreamTest.java create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutOutputStreamTest.java create mode 100644 org.eclipse.jgit/.classpath create mode 100644 org.eclipse.jgit/.fbprefs create mode 100644 org.eclipse.jgit/.gitignore create mode 100644 org.eclipse.jgit/.project create mode 100644 org.eclipse.jgit/.settings/org.eclipse.core.resources.prefs create mode 100644 org.eclipse.jgit/.settings/org.eclipse.core.runtime.prefs create mode 100644 org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs create mode 100644 org.eclipse.jgit/.settings/org.eclipse.jdt.ui.prefs create mode 100644 org.eclipse.jgit/META-INF/MANIFEST.MF create mode 100644 org.eclipse.jgit/build.properties create mode 100644 org.eclipse.jgit/findBugs/FindBugsExcludeFilter.xml create mode 100644 org.eclipse.jgit/plugin.properties create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/awtui/AwtAuthenticator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/awtui/CommitGraphPane.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/awtui/SwingCommitList.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/diff/EditList.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/diff/Sequence.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/CheckoutConflictException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/CompoundException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/ConfigInvalidException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/EntryExistsException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/GitlinksNotSupportedException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/IncorrectObjectTypeException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidPatternException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/MissingBundlePrerequisiteException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/MissingObjectException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/NoClosingBracketException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/NoRemoteRepositoryException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/NotSupportedException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/ObjectWritingException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/PackProtocolException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/RepositoryNotFoundException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/RevWalkException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/RevisionSyntaxException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/StopWalkException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/SymlinksNotSupportedException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/TransportException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/errors/UnmergedPathException.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/WildCardHead.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/AbstractIndexTreeVisitor.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/AlternateRepositoryDatabase.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/BinaryDelta.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteArrayWindow.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteBufferWindow.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/Commit.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaOfsPackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaPackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/DeltaRefPackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ForceModified.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexChangedEvent.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexTreeVisitor.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexTreeWalker.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/InflaterCache.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDirectory.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/OffsetCache.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackFile.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV1.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackLock.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackOutputStream.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackWriter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RefLogWriter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RefsChangedEvent.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryConfig.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryListener.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/Tag.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/TransferConfig.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeIterator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeVisitor.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeVisitorWithCurrentDirectory.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/Treeish.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/UserConfig.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/WholePackedObjectLoader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCache.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCacheConfig.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/WindowCursor.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkDirCheckout.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/lib/WriteTree.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeStrategy.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/patch/BinaryHunk.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/patch/HunkHeader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/patch/Patch.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revplot/AbstractPlotRenderer.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotLane.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockObjQueue.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BoundaryGenerator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DelayRevQueue.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/EndGenerator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FixUninterestingGenerator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/Generator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/LIFORevQueue.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PendingGenerator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevBlob.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitList.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlagSet.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevSort.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTree.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AndRevFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AuthorRevFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitterRevFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MessageRevFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/NotRevFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/OrRevFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/PatternMatchRevFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SubStringRevFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/Daemon.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonClient.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/DaemonService.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchConnection.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchHeadRecord.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchResult.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpTransport.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/IndexPack.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/LongMap.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/OpenSshConfig.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/PackTransport.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHook.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHook.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/PushResult.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigSessionFactory.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/TagOpt.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/TcpTransport.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundle.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleStream.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkTransport.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathSuffixFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java5.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java6.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/MutableInteger.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutInputStream.java create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutOutputStream.java create mode 100644 tag_jgit.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..f57840b7e --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.java diff=java diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..d73c6106c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/jgit +/jgit.jar +/jgit_src.zip +/jgit_docs.zip +/org.eclipse.jgit/lib/jsch-*.jar +/org.eclipse.jgit.pgm/lib/args4j-*.jar diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..a628e2e3d --- /dev/null +++ b/LICENSE @@ -0,0 +1,42 @@ +/* + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + diff --git a/README b/README new file mode 100644 index 000000000..a39adb093 --- /dev/null +++ b/README @@ -0,0 +1,122 @@ + == Java GIT == + +This package is licensed under the BSD. + + org.eclipse.jgit/ + + A pure Java library capable of being run standalone, with no + additional support libraries. Some JUnit tests are provided + to exercise the library. The library provides functions to + read and write a GIT formatted repository. + + All portions of jgit are covered by the BSD. Absolutely no GPL, + LGPL or EPL contributions are accepted within this package. + + org.eclipse.jgit.test/ + Unit tests for org.eclipse.jgit and the same licensing rules. + + == WARNINGS / CAVEATS == + +- Symbolic links are not supported because java does not support it. + Such links could be damaged. + +- Only the timestamp of the index is used by jgit check if the index + is dirty. + +- Don't try the library with a JDK other than 1.6 (Java 6) unless you + are prepared to investigate problems yourself. JDK 1.5.0_11 and later + Java 5 versions *may* work. Earlier versions do not. JDK 1.4 is *not* + supported. Apple's Java 1.5.0_07 is reported to work acceptably. We + have no information about other vendors. Please report your findings + if you try. + +- CRLF conversion is never performed. On Windows you should thereforc + make sure your projects and workspaces are configured to save files + with Unix (LF) line endings. + + == Package Features == + + org.eclipse.jgit/ + + * Read loose and packed commits, trees, blobs, including + deltafied objects. + + * Read objects from shared repositories + + * Write loose commits, trees, blobs. + + * Write blobs from local files or Java InputStreams. + + * Read blobs as Java InputStreams. + + * Copy trees to local directory, or local directory to a tree. + + * Lazily loads objects as necessary. + + * Read and write .git/config files. + + * Create a new repository. + + * Read and write refs, including walking through symrefs. + + * Read, update and write the Git index. + + * Checkout in dirty working directory if trivial. + + * Walk the history from a given set of commits looking for commits + introducing changes in files under a specified path. + + * Object transport + Fetch via ssh, git, http, Amazon S3 and bundles. + Push via ssh, git and Amazon S3. JGit does not yet deltify + the pushed packs so they may be a lot larger than C Git packs. + + org.eclipse.jgit.pgm/ + + * Assorted set of command line utilities. Mostly for ad-hoc testing of jgit + log, glog, fetch etc. + + == Missing Features == + +There are a lot of missing features. You need the real Git for this. +For some operations it may just be the preferred solution also. There +are not just a command line, there is e.g. git-gui that makes committing +partial files simple. + +- Merging. + +- Repacking. + +- Generate a GIT format patch. + +- Apply a GIT format patch. + +- Documentation. :-) + +- gitattributes support + In particular CRLF conversion is not implemented. Files are treated + as byte sequences. + +- submodule support + Submodules are not supported or even recognized. + + == Support == + + Post question, comments or patches to the git@vger.kernel.org mailing list. + + + == Contributing == + + See SUBMITTING_PATCHES in this directory. However, feedback and bug reports + are also contributions. + + + == About GIT == + +More information about GIT, its repository format, and the canonical +C based implementation can be obtained from the GIT websites: + + http://git.or.cz/ + http://www.kernel.org/pub/software/scm/git/ + http://www.kernel.org/pub/software/scm/git/docs/ + diff --git a/SUBMITTING_PATCHES b/SUBMITTING_PATCHES new file mode 100644 index 000000000..82d2c253d --- /dev/null +++ b/SUBMITTING_PATCHES @@ -0,0 +1,366 @@ +Short Version: + + - Make small logical changes. + - Provide a meaningful commit message. + + - Include your Signed-Off-By line to note you agree with the + Developer's Certificate of Origin (see below). + - Make sure all code is under the proper license: + + 3-clause BSD + + - Use a subject prefix of "[PATCH JGIT ...]" when sending any + patches directly by email. + + - Send by email to the maintainers, cc'ing the git mailing list + which is currently used for both Git and JGit: + + maintainers : "Shawn O. Pearce" + Robin Rosenberg + + git list : git@vger.kernel.org + + git list info : http://vger.kernel.org/vger-lists.html#git + +Long Version: + +I wanted a file describing how to submit patches for JGit, +so I started with the one found in the core Git distribution +(Documentation/SubmittingPatches), which itself was based on the +patch submission guidelines for the Linux kernel. + +However there are some differences, so please review and familiarize +yourself with the following relevant bits: + + +(1) Make separate commits for logically separate changes. + +Unless your patch is really trivial, you should not be sending +out a patch that was generated between your working tree and your +commit head. Instead, always make a commit with complete commit +message and generate a series of patches from your repository. +It is a good discipline. + +Describe the technical detail of the change(s). + +If your description starts to get too long, that's a sign that you +probably need to split up your commit to finer grained pieces. + +I am very picky about formatting. Make sure your final version +of every file was formatted using the Eclipse code formatter +using the project specific settings (Properties->Java Code +Style->Formatter->"Java Conventions [built-in]"). + + +(2) Generate your patch using git tools out of your commits. + +git based diff tools (git, and StGIT included) generate unidiff, +which is the only acceptable format. + +You do not have to be afraid to use -M option to "git diff" or "git +format-patch", if your patch involves file renames. The receiving +end can handle them just fine. + +Please make sure your patch does not include any extra files which +do not belong in a patch submission. Make sure to review your +patch after generating it, to ensure accuracy. Before sending out, +please make sure it cleanly applies to the "master" branch head. + + +(3) Sending your patches. + +People on the git mailing list need to be able to read and comment +on the changes you are submitting. It is important for a developer +to be able to "quote" your changes, using standard e-mail tools, so +that they may comment on specific portions of your code. For this +reason, all patches should be submitted "inline". WARNING: Be wary +of your MUAs word-wrap corrupting your patch. Do not cut-n-paste +your patch; you can lose tabs that way if you are not careful. + +It is a common convention to prefix your subject line with [PATCH]. +This lets people easily distinguish patches from other e-mail +discussions. + +"git format-patch" command follows the best current practice to +format the body of an e-mail message. At the beginning of the patch +should come your commit message, ending with the Signed-off-by: +lines, and a line that consists of three dashes, followed by the +diffstat information and the patch itself. If you are forwarding a +patch from somebody else, optionally, at the beginning of the e-mail +message just before the commit message starts, you can put a "From: +" line to name that person. + +You often want to add additional explanation about the patch, +other than the commit message itself. Place such "cover letter" +material between the three dash lines and the diffstat. + +Do not attach the patch as a MIME attachment, compressed or not. +Do not let your e-mail client send quoted-printable. Do not let your +e-mail client send format=flowed which would destroy whitespaces +in your patches. Many popular e-mail applications will not always +transmit a MIME attachment as plain text, making it impossible to +comment on your code. A MIME attachment also takes a bit more +time to process. This does not decrease the likelihood of your +MIME-attached change being accepted, but it makes it more likely +that it will be postponed. + +Exception: If your mailer is mangling patches then someone may ask +you to re-send them using MIME, that is OK. + +Do not PGP sign your patch, at least for now. Most likely, your +maintainer or other people on the list would not have your PGP +key and would not bother obtaining it anyway. Your patch is not +judged by who you are; a good patch from an unknown origin has a +far better chance of being accepted than a patch from a known, +respected origin that is done poorly or does incorrect things. + +If you really really really really want to do a PGP signed +patch, format it as "multipart/signed", not a text/plain message +that starts with '-----BEGIN PGP SIGNED MESSAGE-----'. That is +not a text/plain, it's something else. + +Note that your maintainer does not necessarily read everything +on the git mailing list. If your patch is for discussion first, +send it "To:" the mailing list, and optionally "cc:" him. If it +is trivially correct or after the list reached a consensus, send it +"To:" the maintainer and optionally "cc:" the list. + + +(4) Check the license + +JGit is licensed under the 3-clause (new-style) BSD. + +Because of this licensing model *every* file within the project +*must* list which license covers it in the header of the file. +Any new contributions to an existing file *must* be submitted under +the current license of that file. Any new files *must* clearly +indicate which license they are provided under in the file header. + +Please verify that you are legally allowed and willing to submit your +changes under the license covering each file *prior* to submitting +your patch. It is virtually impossible to remove a patch once it +has been applied and pushed out. + + +(5) Sign your work + +To improve tracking of who did what, we've borrowed the "sign-off" +procedure from the Linux kernel project on patches that are being +emailed around. Although JGit is a lot smaller project it is +a good discipline to follow it. + +The sign-off is a simple line at the end of the explanation for the +patch, which certifies that you wrote it or otherwise have the right +to pass it on as a open-source patch. The rules are pretty simple: +if you can certify the below: + + Developer's Certificate of Origin 1.1 + + By making a contribution to this project, I certify that: + + (a) The contribution was created in whole or in part by me + and I have the right to submit it under the open source + license indicated in the file; or + + (b) The contribution is based upon previous work that, to the + best of my knowledge, is covered under an appropriate + open source license and I have the right under that + license to submit that work with modifications, whether + created in whole or in part by me, under the same open + source license (unless I am permitted to submit under + a different license), as indicated in the file; or + + (c) The contribution was provided directly to me by some + other person who certified (a), (b) or (c) and I have + not modified it. + + (d) I understand and agree that this project and the + contribution are public and that a record of the + contribution (including all personal information I + submit with it, including my sign-off) is maintained + indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + +then you just add a line saying + + Signed-off-by: Random J Developer + +This line can be automatically added by git if you run the git-commit +command with the -s option. + +Some people also put extra tags at the end. They'll just be ignored +for now, but you can do this to mark internal company procedures +or just point out some special detail about the sign-off. + + +------------------------------------------------ +MUA specific hints + +Some of patches I receive or pick up from the list share common +patterns of breakage. Please make sure your MUA is set up +properly not to corrupt whitespaces. Here are two common ones +I have seen: + +* Empty context lines that do not have _any_ whitespace. + +* Non empty context lines that have one extra whitespace at the + beginning. + +One test you could do yourself if your MUA is set up correctly is: + +* Send the patch to yourself, exactly the way you would, except + To: and Cc: lines, which would not contain the list and + maintainer address. + +* Save that patch to a file in UNIX mailbox format. Call it say + a.patch. + +* Try to apply to the tip of the "master" branch from the + egit.git public repository: + + $ git fetch git://repo.or.cz/egit.git master:test-apply + $ git checkout test-apply + $ git reset --hard + $ git am a.patch + +If it does not apply correctly, there can be various reasons. + +* Your patch itself does not apply cleanly. That is _bad_ but + does not have much to do with your MUA. Please rebase the + patch appropriately. + +* Your MUA corrupted your patch; applymbox would complain that + the patch does not apply. Look at .dotest/ subdirectory and + see what 'patch' file contains and check for the common + corruption patterns mentioned above. + +* While you are at it, check what are in 'info' and + 'final-commit' files as well. If what is in 'final-commit' is + not exactly what you would want to see in the commit log + message, it is very likely that your maintainer would end up + hand editing the log message when he applies your patch. + Things like "Hi, this is my first patch.\n", if you really + want to put in the patch e-mail, should come after the + three-dash line that signals the end of the commit message. + + +Pine +---- + +(Johannes Schindelin) + +I don't know how many people still use pine, but for those poor +souls it may be good to mention that the quell-flowed-text is +needed for recent versions. + +... the "no-strip-whitespace-before-send" option, too. AFAIK it +was introduced in 4.60. + +(Linus Torvalds) + +And 4.58 needs at least this. + +--- +diff-tree 8326dd8350be64ac7fc805f6563a1d61ad10d32c (from e886a61f76edf5410573e92e38ce22974f9c40f1) +Author: Linus Torvalds +Date: Mon Aug 15 17:23:51 2005 -0700 + + Fix pine whitespace-corruption bug + + There's no excuse for unconditionally removing whitespace from + the pico buffers on close. + +diff --git a/pico/pico.c b/pico/pico.c +--- a/pico/pico.c ++++ b/pico/pico.c +@@ -219,7 +219,9 @@ PICO *pm; + switch(pico_all_done){ /* prepare for/handle final events */ + case COMP_EXIT : /* already confirmed */ + packheader(); ++#if 0 + stripwhitespace(); ++#endif + c |= COMP_EXIT; + break; + + +(Daniel Barkalow) + +> A patch to SubmittingPatches, MUA specific help section for +> users of Pine 4.63 would be very much appreciated. + +Ah, it looks like a recent version changed the default behavior to do the +right thing, and inverted the sense of the configuration option. (Either +that or Gentoo did it.) So you need to set the +"no-strip-whitespace-before-send" option, unless the option you have is +"strip-whitespace-before-send", in which case you should avoid checking +it. + + +Thunderbird +----------- + +(A Large Angry SCM) + +Here are some hints on how to successfully submit patches inline using +Thunderbird. + +This recipe appears to work with the current [*1*] Thunderbird from Suse. + +The following Thunderbird extensions are needed: + AboutConfig 0.5 + http://aboutconfig.mozdev.org/ + External Editor 0.7.2 + http://globs.org/articles.php?lng=en&pg=8 + +1) Prepare the patch as a text file using your method of choice. + +2) Before opening a compose window, use Edit->Account Settings to +uncheck the "Compose messages in HTML format" setting in the +"Composition & Addressing" panel of the account to be used to send the +patch. [*2*] + +3) In the main Thunderbird window, _before_ you open the compose window +for the patch, use Tools->about:config to set the following to the +indicated values: + mailnews.send_plaintext_flowed => false + mailnews.wraplength => 0 + +4) Open a compose window and click the external editor icon. + +5) In the external editor window, read in the patch file and exit the +editor normally. + +6) Back in the compose window: Add whatever other text you wish to the +message, complete the addressing and subject fields, and press send. + +7) Optionally, undo the about:config/account settings changes made in +steps 2 & 3. + + +[Footnotes] +*1* Version 1.0 (20041207) from the MozillaThunderbird-1.0-5 rpm of Suse +9.3 professional updates. + +*2* It may be possible to do this with about:config and the following +settings but I haven't tried, yet. + mail.html_compose => false + mail.identity.default.compose_html => false + mail.identity.id?.compose_html => false + + + +Gnus +---- + +'|' in the *Summary* buffer can be used to pipe the current +message to an external program, and this is a handy way to drive +"git am". However, if the message is MIME encoded, what is +piped into the program is the representation you see in your +*Article* buffer after unwrapping MIME. This is often not what +you would want for two reasons. It tends to screw up non ASCII +characters (most notably in people's names), and also +whitespaces (fatal in patches). Running 'C-u g' to display the +message in raw form before using '|' to run the pipe can work +this problem around. + diff --git a/TODO b/TODO new file mode 100644 index 000000000..c4ce95e77 --- /dev/null +++ b/TODO @@ -0,0 +1,49 @@ += JGit Wishlist = + +Below are some of the areas that needs work. Also take a look at JGit +related issues in the EGit project: + + - http://code.google.com/p/egit/issues/list?q=label:Component-JGit + +== Switch Branch == + +Switch to an existing branch, updating the working directory to match. + +Note that updating the working directory may require a 3 way merge +if the working directory is dirty (git checkout -m). + +My usual git working style is to not switch branches with a dirty +working directory; I always commit to the current branch before +switching to a new one. I mention that because I assume it'll be +easier to implement that workflow first; once you have commit +capability, you can do that style of branch switching (either +preventing the switch or doing an implicit commit when the working +directory is dirty) without having to worry about merging. ''-- +Steven Grimm'' + +== Merge == + +Merging changes from one local branch to another. + +Again, like fetch I'd like to keep egit/jgit 100% pure Java and +implement merge-recursive in Java. We may need to invoke RCS +merge if Eclipse doesn't have its own 3 way file merge algorithm +available, or do what core Git just did and implement a 3 way in +memory merge algorithm. git-merge-recursive is only 1336 lines of C +so it should not be too difficult to port the algorithm to pure Java. + +== SVN Integration == + +It would be swell -- but put it at the bottom of your priority list +-- to have git-svn interoperability; sadly most of my git usage at +the moment is in cloned svn repositories and it would be great if +egit could do the right thing when the current git repo is cloned +from svn. What "the right thing" is, exactly, is debatable, but I +suppose some kind of integration with the Subclipse plugin is one +possibility (and if nothing else, that plugin probably has code +that can be reused.) I'd like to be able to update from and commit +to the parent svn repository. ''-- Steven Grimm'' + +I'm considering this to be out of scope for the time being, but if +someone takes it on and submits reasonable patches we'll include +them. ''-- Shawn Pearce'' diff --git a/jgit-maven/.gitignore b/jgit-maven/.gitignore new file mode 100644 index 000000000..eb5a316cb --- /dev/null +++ b/jgit-maven/.gitignore @@ -0,0 +1 @@ +target diff --git a/jgit-maven/jgit/pom.xml b/jgit-maven/jgit/pom.xml new file mode 100644 index 000000000..26799778a --- /dev/null +++ b/jgit-maven/jgit/pom.xml @@ -0,0 +1,226 @@ + + + + + 4.0.0 + org.eclipse + jgit + jar + 0.5.0-SNAPSHOT + jgit + http://repo.or.cz/w/jgit.git + + + GIT Mailing List + git@vger.kernel.org + http://marc.info/?l=git + + + Pure Java implementation of Git + + + Shawn O. Pearce + spearce@spearce.org + + Maintainer + + + + Robin Rosenberg + robin.rosenberg@dewire.com + + Maintainer + + + + Dave Watson + dwatson@mimvista.com + + Developer + + + + Roger C. Soares + rogersoares@intelinet.com.br + + Developer + + + + Marek Zawirski + marek.zawirski@gmail.com + + Developer + + + + Charles O'Farrell + charleso@charleso.org + + Contributor + + + + Imran M Yousuf + imyousuf@smartitengineering.com + Smart IT Engineering + + Contributor + + + + + + Eclipse Distribution License (New BSD License) + + All rights reserved. + Redistribution and use in source and binary forms, with or + without modification, are permitted provided that the following + conditions are met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + Neither the name of the Eclipse Foundation, Inc. nor the + names of its contributors may be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + + + ../../org.eclipse.jgit/src/ + + + ../../org.eclipse.jgit.test/tst-rsrc/ + + + ../../org.eclipse.jgit.test/tst/ + + + maven-compiler-plugin + 2.0.2 + + 1.5 + 1.5 + UTF-8 + + + + maven-surefire-plugin + 2.4.2 + + + **/*Test.java + **/*TestCase.java + **/T000*.java + + + + + maven-source-plugin + + + + jar + + + + + + maven-javadoc-plugin + + + + jar + + + + + + + + + junit + junit + 3.8.1 + test + + + com.jcraft + jsch + 0.1.41 + compile + + + + + jgit-maven-snapshot-repository + JGit Maven Snapshot Repository + dav:https://egit.googlecode.com/svn/maven/snapshot-repository/ + true + + + diff --git a/jgit.sh b/jgit.sh new file mode 100644 index 000000000..76b582919 --- /dev/null +++ b/jgit.sh @@ -0,0 +1,94 @@ +#!/bin/sh +# Copyright (C) 2008-2009, Google Inc. +# Copyright (C) 2008, Shawn O. Pearce +# and other copyright owners as documented in the project's IP log. +# +# This program and the accompanying materials are made available +# under the terms of the Eclipse Distribution License v1.0 which +# accompanies this distribution, is reproduced below, and is +# available at http://www.eclipse.org/org/documents/edl-v10.php +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or +# without modification, are permitted provided that the following +# conditions are met: +# +# - Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# - Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# - Neither the name of the Eclipse Foundation, Inc. nor the +# names of its contributors may be used to endorse or promote +# products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +if [ "@@use_self@@" = "1" ] +then + this_script=`which "$0" 2>/dev/null` + [ $? -gt 0 -a -f "$0" ] && this_script="$0" + cp=$this_script +else + jgit_home=`dirname $0` + cp="$jgit_home/org.eclipse.jgit/bin" + cp="$cp:$jgit_home/org.eclipse.jgit/lib/jsch-0.1.37.jar" + cp="$cp:$jgit_home/org.eclipse.jgit.pgm/bin" + cp="$cp:$jgit_home/org.eclipse.jgit.pgm/lib/args4j-2.0.9.jar" + unset jgit_home + java_args= +fi + +if [ -n "$JGIT_CLASSPATH" ] +then + cp="$cp:$JGIT_CLASSPATH" +fi + +# Cleanup paths for Cygwin. +# +case "`uname`" in +CYGWIN*) + cp=`cygpath --windows --mixed --path "$cp"` + ;; +Darwin) + if test -e /System/Library/Frameworks/JavaVM.framework + then + java_args=' + -Dcom.apple.mrj.application.apple.menu.about.name=JGit + -Dcom.apple.mrj.application.growbox.intrudes=false + -Dapple.laf.useScreenMenuBar=true + -Xdock:name=JGit + ' + fi + ;; +esac + +CLASSPATH="$cp" +export CLASSPATH + +java=java +if test -n "$JAVA_HOME" +then + java="$JAVA_HOME/bin/java" +fi + +exec "$java" $java_args org.eclipse.jgit.pgm.Main "$@" +exit 1 diff --git a/make_jgit.sh b/make_jgit.sh new file mode 100644 index 000000000..ae946430c --- /dev/null +++ b/make_jgit.sh @@ -0,0 +1,179 @@ +#!/bin/sh +# Copyright (C) 2009, Christian Halstrick +# Copyright (C) 2008-2009, Google Inc. +# Copyright (C) 2009, Johannes Schindelin +# Copyright (C) 2008, Mike Ralphson +# Copyright (C) 2009, Nicholas Campbell +# Copyright (C) 2009, Robin Rosenberg +# Copyright (C) 2008, Shawn O. Pearce +# and other copyright owners as documented in the project's IP log. +# +# This program and the accompanying materials are made available +# under the terms of the Eclipse Distribution License v1.0 which +# accompanies this distribution, is reproduced below, and is +# available at http://www.eclipse.org/org/documents/edl-v10.php +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or +# without modification, are permitted provided that the following +# conditions are met: +# +# - Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# - Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# - Neither the name of the Eclipse Foundation, Inc. nor the +# names of its contributors may be used to endorse or promote +# products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +O_CLI=jgit +O_JAR=jgit.jar +O_SRC=jgit_src.zip +O_DOC=jgit_docs.zip + +PLUGINS=" + org.eclipse.jgit + org.eclipse.jgit.pgm +" +JARS=" + org.eclipse.jgit/lib/jsch-0.1.37.jar + org.eclipse.jgit.pgm/lib/args4j-2.0.9.jar +" + +PSEP=":" +T=".temp$$.$O_CLI" +T_MF="$T.MF" +R=`pwd` +if [ "$OSTYPE" = "cygwin" ] +then + R=`cygpath -m $R` + PSEP=";" +fi +if [ "$MSYSTEM" = "MINGW" -o "$MSYSTEM" = "MINGW32" ] +then + PSEP=";" + R=`pwd -W` +fi + +if [ -n "$JAVA_HOME" ] +then + PATH=${JAVA_HOME}/bin${PSEP}${PATH} +fi + +cleanup_bin() { + rm -f $T $O_CLI+ $O_JAR+ $O_SRC+ $T_MF + for p in $PLUGINS + do + rm -rf $p/bin2 + done + rm -rf docs +} + +die() { + cleanup_bin + rm -f $O_CLI $O_JAR $O_SRC + echo >&2 "$@" + exit 1 +} + +cleanup_bin +rm -f $O_CLI $O_JAR $O_SRC $O_DOC + +VN=`git describe --abbrev=4 HEAD 2>/dev/null` +git update-index -q --refresh +if [ -n "`git diff-index --name-only HEAD --`" ] +then + VN="$VN-dirty" +fi +VN=${VN:-untagged}`echo "$VN" | sed -e s/-/./g` + +CLASSPATH= +for j in $JARS +do + if [ -z "$CLASSPATH" ] + then + CLASSPATH="$R/$j" + else + CLASSPATH="${CLASSPATH}${PSEP}$R/$j" + fi +done +export CLASSPATH + +for p in $PLUGINS +do + echo "Entering $p ..." + (cd $p/src && + mkdir ../bin2 && + find . -name \*.java -type f | + xargs javac \ + -source 1.5 \ + -target 1.5 \ + -encoding UTF-8 \ + -g \ + -d ../bin2) || die "Building $p failed." + CLASSPATH="${CLASSPATH}${PSEP}$R/$p/bin2" +done +echo + +echo "Version $VN" && +echo Manifest-Version: 1.0 >$T_MF && +echo Implementation-Title: jgit >>$T_MF && +echo Implementation-Version: $VN >>$T_MF && + +java org.eclipse.jgit.pgm.build.JarLinkUtil \ + -include org.eclipse.jgit/bin2 \ + -file META-INF/MANIFEST.MF=$T_MF \ + >$O_JAR+ && +mv $O_JAR+ $O_JAR && +echo "Created $O_JAR." && + +java org.eclipse.jgit.pgm.build.JarLinkUtil \ + -include org.eclipse.jgit/src \ + -file META-INF/MANIFEST.MF=$T_MF \ + >$O_SRC+ && +mv $O_SRC+ $O_SRC && +echo "Created $O_SRC." && + +M_TB=META-INF/services/org.eclipse.jgit.pgm.TextBuiltin && +sed s/@@use_self@@/1/ jgit.sh >$O_CLI+ && +java org.eclipse.jgit.pgm.build.JarLinkUtil \ + `for p in $JARS ; do printf %s " -include $p" ;done` \ + `for p in $PLUGINS; do printf %s " -include $p/bin2";done` \ + -file $M_TB=org.eclipse.jgit.pgm/src/$M_TB \ + -file META-INF/MANIFEST.MF=$T_MF \ + >>$O_CLI+ && +chmod 555 $O_CLI+ && +mv $O_CLI+ $O_CLI && +echo "Created $O_CLI." || die "Build failed." + +echo "Building Javadocs ..." +for p in $PLUGINS; do + javadoc -quiet -sourcepath "$p/src/" -d "docs/$p/" \ + `find "$p/src" -name "*.java"` +done + +(cd docs && jar cf "../$O_DOC" .) +echo "Created $O_DOC." + +cleanup_bin diff --git a/org.eclipse.jgit.pgm/.classpath b/org.eclipse.jgit.pgm/.classpath new file mode 100644 index 000000000..a91b30e8b --- /dev/null +++ b/org.eclipse.jgit.pgm/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.eclipse.jgit.pgm/.gitignore b/org.eclipse.jgit.pgm/.gitignore new file mode 100644 index 000000000..5e56e040e --- /dev/null +++ b/org.eclipse.jgit.pgm/.gitignore @@ -0,0 +1 @@ +/bin diff --git a/org.eclipse.jgit.pgm/.project b/org.eclipse.jgit.pgm/.project new file mode 100644 index 000000000..bbaafc261 --- /dev/null +++ b/org.eclipse.jgit.pgm/.project @@ -0,0 +1,17 @@ + + + org.eclipse.jgit.pgm + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/org.eclipse.jgit.pgm/.settings/org.eclipse.core.resources.prefs b/org.eclipse.jgit.pgm/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 000000000..759548b39 --- /dev/null +++ b/org.eclipse.jgit.pgm/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +#Mon Aug 11 16:46:23 PDT 2008 +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/org.eclipse.jgit.pgm/.settings/org.eclipse.core.runtime.prefs b/org.eclipse.jgit.pgm/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 000000000..006e07ede --- /dev/null +++ b/org.eclipse.jgit.pgm/.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.pgm/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..f0c80d363 --- /dev/null +++ b/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,321 @@ +#Sun Mar 15 19:46:39 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=protected +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.pgm/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.pgm/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 000000000..709a44074 --- /dev/null +++ b/org.eclipse.jgit.pgm/.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= diff --git a/org.eclipse.jgit.pgm/src/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/src/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin new file mode 100644 index 000000000..9c8933f5d --- /dev/null +++ b/org.eclipse.jgit.pgm/src/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin @@ -0,0 +1,30 @@ +org.eclipse.jgit.pgm.Branch +org.eclipse.jgit.pgm.Clone +org.eclipse.jgit.pgm.Daemon +org.eclipse.jgit.pgm.DiffTree +org.eclipse.jgit.pgm.Fetch +org.eclipse.jgit.pgm.Glog +org.eclipse.jgit.pgm.IndexPack +org.eclipse.jgit.pgm.Init +org.eclipse.jgit.pgm.Log +org.eclipse.jgit.pgm.LsRemote +org.eclipse.jgit.pgm.LsTree +org.eclipse.jgit.pgm.MergeBase +org.eclipse.jgit.pgm.Push +org.eclipse.jgit.pgm.ReceivePack +org.eclipse.jgit.pgm.RevList +org.eclipse.jgit.pgm.RevParse +org.eclipse.jgit.pgm.Rm +org.eclipse.jgit.pgm.ShowRev +org.eclipse.jgit.pgm.ShowRef +org.eclipse.jgit.pgm.Tag +org.eclipse.jgit.pgm.UploadPack +org.eclipse.jgit.pgm.Version + +org.eclipse.jgit.pgm.debug.MakeCacheTree +org.eclipse.jgit.pgm.debug.ReadDirCache +org.eclipse.jgit.pgm.debug.RebuildCommitGraph +org.eclipse.jgit.pgm.debug.ShowCacheTree +org.eclipse.jgit.pgm.debug.ShowCommands +org.eclipse.jgit.pgm.debug.ShowDirCache +org.eclipse.jgit.pgm.debug.WriteDirCache diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java new file mode 100644 index 000000000..f5e3c504c --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/AbstractFetchCommand.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2008, Charles O'Farrell + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.transport.FetchResult; +import org.eclipse.jgit.transport.TrackingRefUpdate; +import org.eclipse.jgit.transport.Transport; + +abstract class AbstractFetchCommand extends TextBuiltin { + @Option(name = "--verbose", aliases = { "-v" }, usage = "be more verbose") + private boolean verbose; + + protected void showFetchResult(final Transport tn, final FetchResult r) { + boolean shownURI = false; + for (final TrackingRefUpdate u : r.getTrackingRefUpdates()) { + if (!verbose && u.getResult() == RefUpdate.Result.NO_CHANGE) + continue; + + final char type = shortTypeOf(u.getResult()); + final String longType = longTypeOf(u); + final String src = abbreviateRef(u.getRemoteName(), false); + final String dst = abbreviateRef(u.getLocalName(), true); + + if (!shownURI) { + out.print("From "); + out.print(tn.getURI()); + out.println(); + shownURI = true; + } + + out.format(" %c %-17s %-10s -> %s", type, longType, src, dst); + out.println(); + } + } + + private String longTypeOf(final TrackingRefUpdate u) { + final RefUpdate.Result r = u.getResult(); + if (r == RefUpdate.Result.LOCK_FAILURE) + return "[lock fail]"; + if (r == RefUpdate.Result.IO_FAILURE) + return "[i/o error]"; + if (r == RefUpdate.Result.REJECTED) + return "[rejected]"; + if (ObjectId.zeroId().equals(u.getNewObjectId())) + return "[deleted]"; + + if (r == RefUpdate.Result.NEW) { + if (u.getRemoteName().startsWith(Constants.R_HEADS)) + return "[new branch]"; + else if (u.getLocalName().startsWith(Constants.R_TAGS)) + return "[new tag]"; + return "[new]"; + } + + if (r == RefUpdate.Result.FORCED) { + final String aOld = u.getOldObjectId().abbreviate(db).name(); + final String aNew = u.getNewObjectId().abbreviate(db).name(); + return aOld + "..." + aNew; + } + + if (r == RefUpdate.Result.FAST_FORWARD) { + final String aOld = u.getOldObjectId().abbreviate(db).name(); + final String aNew = u.getNewObjectId().abbreviate(db).name(); + return aOld + ".." + aNew; + } + + if (r == RefUpdate.Result.NO_CHANGE) + return "[up to date]"; + return "[" + r.name() + "]"; + } + + private static char shortTypeOf(final RefUpdate.Result r) { + if (r == RefUpdate.Result.LOCK_FAILURE) + return '!'; + if (r == RefUpdate.Result.IO_FAILURE) + return '!'; + if (r == RefUpdate.Result.NEW) + return '*'; + if (r == RefUpdate.Result.FORCED) + return '+'; + if (r == RefUpdate.Result.FAST_FORWARD) + return ' '; + if (r == RefUpdate.Result.REJECTED) + return '!'; + if (r == RefUpdate.Result.NO_CHANGE) + return '='; + return ' '; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java new file mode 100644 index 000000000..60dbe27ac --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2007-2008, Charles O'Farrell + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.ExampleMode; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefComparator; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.pgm.opt.CmdLineParser; +import org.eclipse.jgit.revwalk.RevWalk; + +@Command(common = true, usage = "List, create, or delete branches") +class Branch extends TextBuiltin { + + @Option(name = "--remote", aliases = { "-r" }, usage = "act on remote-tracking branches") + private boolean remote = false; + + @Option(name = "--all", aliases = { "-a" }, usage = "list both remote-tracking and local branches") + private boolean all = false; + + @Option(name = "--delete", aliases = { "-d" }, usage = "delete fully merged branch") + private boolean delete = false; + + @Option(name = "--delete-force", aliases = { "-D" }, usage = "delete branch (even if not merged)") + private boolean deleteForce = false; + + @Option(name = "--create-force", aliases = { "-f" }, usage = "force create branch even exists") + private boolean createForce = false; + + @Option(name = "--verbose", aliases = { "-v" }, usage = "be verbose") + private boolean verbose = false; + + @Argument + private List branches = new ArrayList(); + + private final Map printRefs = new LinkedHashMap(); + + /** Only set for verbose branch listing at-the-moment */ + private RevWalk rw; + + private int maxNameLength; + + @Override + protected void run() throws Exception { + if (delete || deleteForce) + delete(deleteForce); + else { + if (branches.size() > 2) + throw die("Too many refs given\n" + new CmdLineParser(this).printExample(ExampleMode.ALL)); + + if (branches.size() > 0) { + String newHead = branches.get(0); + String startBranch; + if (branches.size() == 2) + startBranch = branches.get(1); + else + startBranch = Constants.HEAD; + Ref startRef = db.getRef(startBranch); + ObjectId startAt = db.resolve(startBranch + "^0"); + if (startRef != null) + startBranch = startRef.getName(); + else + startBranch = startAt.name(); + startBranch = db.shortenRefName(startBranch); + String newRefName = newHead; + if (!newRefName.startsWith(Constants.R_HEADS)) + newRefName = Constants.R_HEADS + newRefName; + if (!Repository.isValidRefName(newRefName)) + throw die(String.format("%s is not a valid ref name", newRefName)); + if (!createForce && db.resolve(newRefName) != null) + throw die(String.format("branch %s already exists", newHead)); + RefUpdate updateRef = db.updateRef(newRefName); + updateRef.setNewObjectId(startAt); + updateRef.setForceUpdate(createForce); + updateRef.setRefLogMessage("branch: Created from " + startBranch, false); + Result update = updateRef.update(); + if (update == Result.REJECTED) + throw die(String.format("Could not create branch %s: %s", newHead, update.toString())); + } else { + if (verbose) + rw = new RevWalk(db); + list(); + } + } + } + + private void list() throws Exception { + Map refs = db.getAllRefs(); + Ref head = refs.get(Constants.HEAD); + // This can happen if HEAD is stillborn + if (head != null) { + String current = head.getName(); + if (current.equals(Constants.HEAD)) + addRef("(no branch)", head); + addRefs(refs, Constants.R_HEADS, !remote); + addRefs(refs, Constants.R_REMOTES, remote); + for (final Entry e : printRefs.entrySet()) { + final Ref ref = e.getValue(); + printHead(e.getKey(), current.equals(ref.getName()), ref); + } + } + } + + private void addRefs(final Map allRefs, final String prefix, + final boolean add) { + if (all || add) { + for (final Ref ref : RefComparator.sort(allRefs.values())) { + final String name = ref.getName(); + if (name.startsWith(prefix)) + addRef(name.substring(name.indexOf('/', 5) + 1), ref); + } + } + } + + private void addRef(final String name, final Ref ref) { + printRefs.put(name, ref); + maxNameLength = Math.max(maxNameLength, name.length()); + } + + private void printHead(final String ref, final boolean isCurrent, + final Ref refObj) throws Exception { + out.print(isCurrent ? '*' : ' '); + out.print(' '); + out.print(ref); + if (verbose) { + final int spaces = maxNameLength - ref.length() + 1; + out.print(String.format("%" + spaces + "s", "")); + final ObjectId objectId = refObj.getObjectId(); + out.print(objectId.abbreviate(db).name()); + out.print(' '); + out.print(rw.parseCommit(objectId).getShortMessage()); + } + out.println(); + } + + private void delete(boolean force) throws IOException { + String current = db.getBranch(); + ObjectId head = db.resolve(Constants.HEAD); + for (String branch : branches) { + if (current.equals(branch)) { + String err = "Cannot delete the branch '%s' which you are currently on."; + throw die(String.format(err, branch)); + } + RefUpdate update = db.updateRef((remote ? Constants.R_REMOTES + : Constants.R_HEADS) + + branch); + update.setNewObjectId(head); + update.setForceUpdate(force || remote); + Result result = update.delete(); + if (result == Result.REJECTED) { + String err = "The branch '%s' is not an ancestor of your current HEAD.\n" + + "If you are sure you want to delete it, run 'jgit branch -D %1$s'."; + throw die(String.format(err, branch)); + } else if (result == Result.NEW) + throw die(String.format("branch '%s' not found.", branch)); + if (remote) + out.println(String.format("Deleted remote branch %s", branch)); + else if (verbose) + out.println(String.format("Deleted branch %s", branch)); + } + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java new file mode 100644 index 000000000..605f9552f --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Clone.java @@ -0,0 +1,187 @@ +/* + * 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.pgm; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.errors.NotSupportedException; +import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.lib.Commit; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.GitIndex; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefComparator; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.TextProgressMonitor; +import org.eclipse.jgit.lib.Tree; +import org.eclipse.jgit.lib.WorkDirCheckout; +import org.eclipse.jgit.transport.FetchResult; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.Transport; +import org.eclipse.jgit.transport.URIish; + +@Command(common = true, usage = "Clone a repository into a new directory") +class Clone extends AbstractFetchCommand { + @Option(name = "--origin", aliases = { "-o" }, metaVar = "name", usage = "use instead of 'origin' to track upstream") + private String remoteName = "origin"; + + @Argument(index = 0, required = true, metaVar = "uri-ish") + private String sourceUri; + + @Argument(index = 1, metaVar = "directory") + private String localName; + + @Override + protected final boolean requiresRepository() { + return false; + } + + @Override + protected void run() throws Exception { + if (localName != null && gitdir != null) + throw die("conflicting usage of --git-dir and arguments"); + + final URIish uri = new URIish(sourceUri); + if (localName == null) { + String p = uri.getPath(); + while (p.endsWith("/")) + p = p.substring(0, p.length() - 1); + final int s = p.lastIndexOf('/'); + if (s < 0) + throw die("cannot guess local name from " + sourceUri); + localName = p.substring(s + 1); + if (localName.endsWith(".git")) + localName = localName.substring(0, localName.length() - 4); + } + if (gitdir == null) + gitdir = new File(localName, ".git"); + + db = new Repository(gitdir); + db.create(); + db.getConfig().setBoolean("core", null, "bare", false); + db.getConfig().save(); + + out.println("Initialized empty Git repository in " + + gitdir.getAbsolutePath()); + out.flush(); + + saveRemote(uri); + final FetchResult r = runFetch(); + final Ref branch = guessHEAD(r); + doCheckout(branch); + } + + private void saveRemote(final URIish uri) throws URISyntaxException, + IOException { + final RemoteConfig rc = new RemoteConfig(db.getConfig(), remoteName); + rc.addURI(uri); + rc.addFetchRefSpec(new RefSpec().setForceUpdate(true) + .setSourceDestination(Constants.R_HEADS + "*", + Constants.R_REMOTES + remoteName + "/*")); + rc.update(db.getConfig()); + db.getConfig().save(); + } + + private FetchResult runFetch() throws NotSupportedException, + URISyntaxException, TransportException { + final Transport tn = Transport.open(db, remoteName); + final FetchResult r; + try { + r = tn.fetch(new TextProgressMonitor(), null); + } finally { + tn.close(); + } + showFetchResult(tn, r); + return r; + } + + private Ref guessHEAD(final FetchResult result) { + final Ref idHEAD = result.getAdvertisedRef(Constants.HEAD); + final List availableRefs = new ArrayList(); + Ref head = null; + for (final Ref r : result.getAdvertisedRefs()) { + final String n = r.getName(); + if (!n.startsWith(Constants.R_HEADS)) + continue; + availableRefs.add(r); + if (idHEAD == null || head != null) + continue; + if (r.getObjectId().equals(idHEAD.getObjectId())) + head = r; + } + Collections.sort(availableRefs, RefComparator.INSTANCE); + if (idHEAD != null && head == null) + head = idHEAD; + return head; + } + + private void doCheckout(final Ref branch) throws IOException { + if (branch == null) + throw die("cannot checkout; no HEAD advertised by remote"); + if (!Constants.HEAD.equals(branch.getName())) + db.writeSymref(Constants.HEAD, branch.getName()); + + final Commit commit = db.mapCommit(branch.getObjectId()); + final RefUpdate u = db.updateRef(Constants.HEAD); + u.setNewObjectId(commit.getCommitId()); + u.forceUpdate(); + + final GitIndex index = new GitIndex(db); + final Tree tree = commit.getTree(); + final WorkDirCheckout co; + + co = new WorkDirCheckout(db, db.getWorkDir(), index, tree); + co.checkout(); + index.write(); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Command.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Command.java new file mode 100644 index 000000000..0562416a8 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Command.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Annotation to document a {@link TextBuiltin}. + *

+ * This is an optional annotation for TextBuiltin subclasses and it carries + * documentation forward into the runtime system describing what the command is + * and why users may want to invoke it. + */ +@Retention(RUNTIME) +@Target( { TYPE }) +public @interface Command { + /** + * @return name the command is invoked as from the command line. If the + * (default) empty string is supplied the name will be generated + * from the class name. + */ + public String name() default ""; + + /** + * @return one line description of the command's feature set. + */ + public String usage() default ""; + + /** + * @return true if this command is considered to be commonly used. + */ + public boolean common() default false; +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandCatalog.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandCatalog.java new file mode 100644 index 000000000..700e54118 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandCatalog.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; + +/** + * List of all commands known by jgit's command line tools. + *

+ * Commands are implementations of {@link TextBuiltin}, with an optional + * {@link Command} class annotation to insert additional documentation or + * override the default command name (which is guessed from the class name). + *

+ * Commands may be registered by adding them to a services file in the same JAR + * (or classes directory) as the command implementation. The service file name + * is META-INF/services/org.eclipse.jgit.pgm.TextBuiltin and it + * contains one concrete implementation class name per line. + *

+ * Command registration is identical to Java 6's services, however the catalog + * uses a lightweight wrapper to delay creating a command instance as much as + * possible. This avoids initializing the AWT or SWT GUI toolkits even if the + * command's constructor might require them. + */ +public class CommandCatalog { + private static final CommandCatalog INSTANCE = new CommandCatalog(); + + /** + * Locate a single command by its user friendly name. + * + * @param name + * name of the command. Typically in dash-lower-case-form, which + * was derived from the DashLowerCaseForm class name. + * @return the command instance; null if no command exists by that name. + */ + public static CommandRef get(final String name) { + return INSTANCE.commands.get(name); + } + + /** + * @return all known commands, sorted by command name. + */ + public static CommandRef[] all() { + return toSortedArray(INSTANCE.commands.values()); + } + + /** + * @return all common commands, sorted by command name. + */ + public static CommandRef[] common() { + final ArrayList common = new ArrayList(); + for (final CommandRef c : INSTANCE.commands.values()) + if (c.isCommon()) + common.add(c); + return toSortedArray(common); + } + + private static CommandRef[] toSortedArray(final Collection c) { + final CommandRef[] r = c.toArray(new CommandRef[c.size()]); + Arrays.sort(r, new Comparator() { + public int compare(final CommandRef o1, final CommandRef o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + return r; + } + + private final ClassLoader ldr; + + private final Map commands; + + private CommandCatalog() { + ldr = Thread.currentThread().getContextClassLoader(); + commands = new HashMap(); + + final Enumeration catalogs = catalogs(); + while (catalogs.hasMoreElements()) + scan(catalogs.nextElement()); + } + + private Enumeration catalogs() { + try { + final String pfx = "META-INF/services/"; + return ldr.getResources(pfx + TextBuiltin.class.getName()); + } catch (IOException err) { + return new Vector().elements(); + } + } + + private void scan(final URL cUrl) { + final BufferedReader cIn; + try { + final InputStream in = cUrl.openStream(); + cIn = new BufferedReader(new InputStreamReader(in, "UTF-8")); + } catch (IOException err) { + // If we cannot read from the service list, go to the next. + // + return; + } + + try { + String line; + while ((line = cIn.readLine()) != null) { + if (line.length() > 0 && !line.startsWith("#")) + load(line); + } + } catch (IOException err) { + // If we failed during a read, ignore the error. + // + } finally { + try { + cIn.close(); + } catch (IOException e) { + // Ignore the close error; we are only reading. + } + } + } + + private void load(final String cn) { + final Class clazz; + try { + clazz = Class.forName(cn, false, ldr).asSubclass(TextBuiltin.class); + } catch (ClassNotFoundException notBuiltin) { + // Doesn't exist, even though the service entry is present. + // + return; + } catch (ClassCastException notBuiltin) { + // Isn't really a builtin, even though its listed as such. + // + return; + } + + final CommandRef cr; + final Command a = clazz.getAnnotation(Command.class); + if (a != null) + cr = new CommandRef(clazz, a); + else + cr = new CommandRef(clazz); + + commands.put(cr.getName(), cr); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandRef.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandRef.java new file mode 100644 index 000000000..eb68ada9b --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/CommandRef.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/** + * Description of a command (a {@link TextBuiltin} subclass. + *

+ * These descriptions are lightweight compared to creating a command instance + * and are therefore suitable for catalogs of "known" commands without linking + * the command's implementation and creating a dummy instance of the command. + */ +public class CommandRef { + private final Class impl; + + private final String name; + + private String usage; + + boolean common; + + CommandRef(final Class clazz) { + this(clazz, guessName(clazz)); + } + + CommandRef(final Class clazz, final Command cmd) { + this(clazz, cmd.name().length() > 0 ? cmd.name() : guessName(clazz)); + usage = cmd.usage(); + common = cmd.common(); + } + + private CommandRef(final Class clazz, final String cn) { + impl = clazz; + name = cn; + usage = ""; + } + + private static String guessName(final Class clazz) { + final StringBuilder s = new StringBuilder(); + if (clazz.getName().startsWith("org.eclipse.jgit.pgm.debug.")) + s.append("debug-"); + + boolean lastWasDash = true; + for (final char c : clazz.getSimpleName().toCharArray()) { + if (Character.isUpperCase(c)) { + if (!lastWasDash) + s.append('-'); + lastWasDash = !lastWasDash; + s.append(Character.toLowerCase(c)); + } else { + s.append(c); + } + } + return s.toString(); + } + + /** + * @return name the command is invoked as from the command line. + */ + public String getName() { + return name; + } + + /** + * @return one line description of the command's feature set. + */ + public String getUsage() { + return usage; + } + + /** + * @return true if this command is considered to be commonly used. + */ + public boolean isCommon() { + return common; + } + + /** + * @return name of the Java class which implements this command. + */ + public String getImplementationClassName() { + return impl.getName(); + } + + /** + * @return loader for {@link #getImplementationClassName()}. + */ + public ClassLoader getImplementationClassLoader() { + return impl.getClassLoader(); + } + + /** + * @return a new instance of the command implementation. + */ + public TextBuiltin create() { + final Constructor c; + try { + c = impl.getDeclaredConstructor(); + } catch (SecurityException e) { + throw new RuntimeException("Cannot create command " + getName(), e); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Cannot create command " + getName(), e); + } + c.setAccessible(true); + + final TextBuiltin r; + try { + r = c.newInstance(); + } catch (InstantiationException e) { + throw new RuntimeException("Cannot create command " + getName(), e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot create command " + getName(), e); + } catch (IllegalArgumentException e) { + throw new RuntimeException("Cannot create command " + getName(), e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Cannot create command " + getName(), e); + } + r.setCommandName(getName()); + return r; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java new file mode 100644 index 000000000..88219bdd9 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Daemon.java @@ -0,0 +1,125 @@ +/* + * 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.pgm; + +import java.io.File; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.transport.DaemonService; + +@Command(common = true, usage = "Export repositories over git://") +class Daemon extends TextBuiltin { + @Option(name = "--port", metaVar = "PORT", usage = "port number to listen on") + int port = org.eclipse.jgit.transport.Daemon.DEFAULT_PORT; + + @Option(name = "--listen", metaVar = "HOSTNAME", usage = "hostname (or ip) to listen on") + String host; + + @Option(name = "--timeout", metaVar = "SECONDS", usage = "abort connection if no activity") + int timeout = -1; + + @Option(name = "--enable", metaVar = "SERVICE", usage = "enable the service in all repositories", multiValued = true) + final List enable = new ArrayList(); + + @Option(name = "--disable", metaVar = "SERVICE", usage = "disable the service in all repositories", multiValued = true) + final List disable = new ArrayList(); + + @Option(name = "--allow-override", metaVar = "SERVICE", usage = "configure the service in daemon.servicename", multiValued = true) + final List canOverride = new ArrayList(); + + @Option(name = "--forbid-override", metaVar = "SERVICE", usage = "configure the service in daemon.servicename", multiValued = true) + final List forbidOverride = new ArrayList(); + + @Option(name = "--export-all", usage = "export without git-daemon-export-ok") + boolean exportAll; + + @Argument(required = true, metaVar = "DIRECTORY", usage = "directories to export") + final List directory = new ArrayList(); + + @Override + protected boolean requiresRepository() { + return false; + } + + @Override + protected void run() throws Exception { + final org.eclipse.jgit.transport.Daemon d; + + d = new org.eclipse.jgit.transport.Daemon( + host != null ? new InetSocketAddress(host, port) + : new InetSocketAddress(port)); + d.setExportAll(exportAll); + if (0 <= timeout) + d.setTimeout(timeout); + + for (final String n : enable) + service(d, n).setEnabled(true); + for (final String n : disable) + service(d, n).setEnabled(false); + + for (final String n : canOverride) + service(d, n).setOverridable(true); + for (final String n : forbidOverride) + service(d, n).setOverridable(false); + + for (final File f : directory) { + out.println("Exporting " + f.getAbsolutePath()); + d.exportDirectory(f); + } + d.start(); + out.println("Listening on " + d.getAddress()); + } + + private DaemonService service(final org.eclipse.jgit.transport.Daemon d, + final String n) { + final DaemonService svc = d.getService(n); + if (svc == null) + throw die("Service '" + n + "' not supported"); + return svc; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Die.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Die.java new file mode 100644 index 000000000..e514ca514 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Die.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +/** + * Indicates a {@link TextBuiltin} implementation has failed during execution. + *

+ * Typically the stack trace for a Die exception is not shown to the user as it + * may indicate a simple error condition that the end-user can fix on their own, + * without needing a screen of Java stack frames. + */ +public class Die extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** + * Construct a new message explaining what has gone wrong. + * + * @param why + * the message to show to the end-user. + */ + public Die(final String why) { + super(why); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java new file mode 100644 index 000000000..13b1c780f --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/DiffTree.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2008, Jonas Fonseca + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import java.util.ArrayList; +import java.util.List; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler; +import org.eclipse.jgit.treewalk.AbstractTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.AndTreeFilter; +import org.eclipse.jgit.treewalk.filter.TreeFilter; + +class DiffTree extends TextBuiltin { + @Option(name = "--recursive", usage = "recurse into subtrees", aliases = { "-r" }) + private boolean recursive; + + @Argument(index = 0, metaVar = "tree-ish", required = true) + void tree_0(final AbstractTreeIterator c) { + trees.add(c); + } + + @Argument(index = 1, metaVar = "tree-ish", required = true) + private final List trees = new ArrayList(); + + @Option(name = "--", metaVar = "path", multiValued = true, handler = PathTreeFilterHandler.class) + private TreeFilter pathFilter = TreeFilter.ALL; + + @Override + protected void run() throws Exception { + final TreeWalk walk = new TreeWalk(db); + walk.reset(); + walk.setRecursive(recursive); + for (final AbstractTreeIterator i : trees) + walk.addTree(i); + walk.setFilter(AndTreeFilter.create(TreeFilter.ANY_DIFF, pathFilter)); + + final int nTree = walk.getTreeCount(); + while (walk.next()) { + for (int i = 1; i < nTree; i++) + out.print(':'); + for (int i = 0; i < nTree; i++) { + final FileMode m = walk.getFileMode(i); + final String s = m.toString(); + for (int pad = 6 - s.length(); pad > 0; pad--) + out.print('0'); + out.print(s); + out.print(' '); + } + + for (int i = 0; i < nTree; i++) { + out.print(walk.getObjectId(i).name()); + out.print(' '); + } + + char chg = 'M'; + if (nTree == 2) { + final int m0 = walk.getRawMode(0); + final int m1 = walk.getRawMode(1); + if (m0 == 0 && m1 != 0) + chg = 'A'; + else if (m0 != 0 && m1 == 0) + chg = 'D'; + else if (m0 != m1 && walk.idEqual(0, 1)) + chg = 'T'; + } + out.print(chg); + + out.print('\t'); + out.print(walk.getPathString()); + out.println(); + } + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Fetch.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Fetch.java new file mode 100644 index 000000000..7315e44cb --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Fetch.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import java.util.List; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.lib.TextProgressMonitor; +import org.eclipse.jgit.transport.FetchResult; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.transport.Transport; + +@Command(common = true, usage = "Update remote refs from another repository") +class Fetch extends AbstractFetchCommand { + @Option(name = "--timeout", metaVar = "SECONDS", usage = "abort connection if no activity") + int timeout = -1; + + @Option(name = "--fsck", usage = "perform fsck style checks on receive") + private Boolean fsck; + + @Option(name = "--no-fsck") + void nofsck(final boolean ignored) { + fsck = Boolean.FALSE; + } + + @Option(name = "--prune", usage = "prune stale tracking refs") + private Boolean prune; + + @Option(name = "--dry-run") + private boolean dryRun; + + @Option(name = "--thin", usage = "fetch thin pack") + private Boolean thin; + + @Option(name = "--no-thin") + void nothin(final boolean ignored) { + thin = Boolean.FALSE; + } + + @Argument(index = 0, metaVar = "uri-ish") + private String remote = "origin"; + + @Argument(index = 1, metaVar = "refspec") + private List toget; + + @Override + protected void run() throws Exception { + final Transport tn = Transport.open(db, remote); + if (fsck != null) + tn.setCheckFetchedObjects(fsck.booleanValue()); + if (prune != null) + tn.setRemoveDeletedRefs(prune.booleanValue()); + tn.setDryRun(dryRun); + if (thin != null) + tn.setFetchThin(thin.booleanValue()); + if (0 <= timeout) + tn.setTimeout(timeout); + final FetchResult r; + try { + r = tn.fetch(new TextProgressMonitor(), toget); + if (r.getTrackingRefUpdates().isEmpty()) + return; + } finally { + tn.close(); + } + showFetchResult(tn, r); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java new file mode 100644 index 000000000..c799222f5 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Glog.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; + +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +import org.eclipse.jgit.awtui.CommitGraphPane; +import org.eclipse.jgit.revplot.PlotWalk; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevSort; +import org.eclipse.jgit.revwalk.RevWalk; + +class Glog extends RevWalkTextBuiltin { + final JFrame frame; + + final CommitGraphPane graphPane; + + Glog() { + frame = new JFrame(); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(final WindowEvent e) { + frame.dispose(); + } + }); + + graphPane = new CommitGraphPane(); + + final JScrollPane graphScroll = new JScrollPane(graphPane); + + final JPanel buttons = new JPanel(new FlowLayout()); + final JButton repaint = new JButton(); + repaint.setText("Repaint"); + repaint.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + graphPane.repaint(); + } + }); + buttons.add(repaint); + + final JPanel world = new JPanel(new BorderLayout()); + world.add(buttons, BorderLayout.SOUTH); + world.add(graphScroll, BorderLayout.CENTER); + + frame.getContentPane().add(world); + } + + @Override + protected int walkLoop() throws Exception { + graphPane.getCommitList().source(walk); + graphPane.getCommitList().fillTo(Integer.MAX_VALUE); + + frame.setTitle("[" + repoName() + "]"); + frame.pack(); + frame.setVisible(true); + return graphPane.getCommitList().size(); + } + + @Override + protected void show(final RevCommit c) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected RevWalk createWalk() { + if (objects) + throw die("Cannot use --objects with glog"); + final PlotWalk w = new PlotWalk(db); + w.sort(RevSort.BOUNDARY, true); + return w; + } + + private String repoName() { + final File f = db.getDirectory(); + String n = f.getName(); + if (".git".equals(n)) + n = f.getParentFile().getName(); + return n; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/IndexPack.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/IndexPack.java new file mode 100644 index 000000000..eb65e680f --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/IndexPack.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import java.io.BufferedInputStream; +import java.io.File; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.lib.TextProgressMonitor; + +class IndexPack extends TextBuiltin { + @Option(name = "--fix-thin", usage = "fix a thin pack to be complete") + private boolean fixThin; + + @Option(name = "--index-version", usage = "index file format to create") + private int indexVersion = -1; + + @Argument(index = 0, required = true, metaVar = "base") + private File base; + + @Override + protected void run() throws Exception { + if (indexVersion == -1) + indexVersion = db.getConfig().getCore().getPackIndexVersion(); + final BufferedInputStream in; + final org.eclipse.jgit.transport.IndexPack ip; + in = new BufferedInputStream(System.in); + ip = new org.eclipse.jgit.transport.IndexPack(db, in, base); + ip.setFixThin(fixThin); + ip.setIndexVersion(indexVersion); + ip.index(new TextProgressMonitor()); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java new file mode 100644 index 000000000..b6a4a44a9 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Init.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * 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.pgm; + +import java.io.File; + +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.lib.Repository; + +@Command(common = true, usage = "Create an empty git repository") +class Init extends TextBuiltin { + @Option(name = "--bare", usage = "Create a bare repository") + private boolean bare; + + @Override + protected final boolean requiresRepository() { + return false; + } + + @Override + protected void run() throws Exception { + if (gitdir == null) + gitdir = new File(bare ? "." : ".git"); + db = new Repository(gitdir); + db.create(bare); + out.println("Initialized empty Git repository in " + + gitdir.getAbsolutePath()); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java new file mode 100644 index 000000000..ecaf19bd1 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2006-2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; + +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; + +@Command(common = true, usage = "View commit history") +class Log extends RevWalkTextBuiltin { + private final TimeZone myTZ = TimeZone.getDefault(); + + private final DateFormat fmt; + + private Map> allRefsByPeeledObjectId; + + @Option(name="--decorate", usage="Show ref names matching commits") + private boolean decorate; + + Log() { + fmt = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy ZZZZZ", Locale.US); + } + + @Override + protected RevWalk createWalk() { + RevWalk ret = super.createWalk(); + if (decorate) + allRefsByPeeledObjectId = getRepository().getAllRefsByPeeledObjectId(); + return ret; + } + + @Override + protected void show(final RevCommit c) throws Exception { + out.print("commit "); + c.getId().copyTo(outbuffer, out); + if (decorate) { + Collection list = allRefsByPeeledObjectId.get(c.copy()); + if (list != null) { + out.print(" ("); + for (Iterator i = list.iterator(); i.hasNext(); ) { + out.print(i.next().getOrigName()); + if (i.hasNext()) + out.print(" "); + } + out.print(")"); + } + } + out.println(); + + final PersonIdent author = c.getAuthorIdent(); + out.print("Author: "); + out.print(author.getName()); + out.print(" <"); + out.print(author.getEmailAddress()); + out.print(">"); + out.println(); + + final TimeZone authorTZ = author.getTimeZone(); + fmt.setTimeZone(authorTZ != null ? authorTZ : myTZ); + out.print("Date: "); + out.print(fmt.format(author.getWhen())); + out.println(); + + out.println(); + final String[] lines = c.getFullMessage().split("\n"); + for (final String s : lines) { + out.print(" "); + out.print(s); + out.println(); + } + + out.println(); + out.flush(); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java new file mode 100644 index 000000000..44d1e754f --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsRemote.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2008, Jonas Fonseca + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.transport.FetchConnection; +import org.eclipse.jgit.transport.Transport; + +class LsRemote extends TextBuiltin { + @Option(name = "--timeout", metaVar = "SECONDS", usage = "abort connection if no activity") + int timeout = -1; + + @Argument(index = 0, metaVar = "uri-ish", required = true) + private String remote; + + @Override + protected void run() throws Exception { + final Transport tn = Transport.open(db, remote); + if (0 <= timeout) + tn.setTimeout(timeout); + final FetchConnection c = tn.openFetch(); + try { + for (final Ref r : c.getRefs()) { + show(r.getObjectId(), r.getName()); + if (r.getPeeledObjectId() != null) + show(r.getPeeledObjectId(), r.getName() + "^{}"); + } + } finally { + c.close(); + tn.close(); + } + } + + private void show(final AnyObjectId id, final String name) { + out.print(id.name()); + out.print('\t'); + out.print(name); + out.println(); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java new file mode 100644 index 000000000..1a28a9a48 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/LsTree.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2008, Jonas Fonseca + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.treewalk.AbstractTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk; + +class LsTree extends TextBuiltin { + @Option(name = "--recursive", usage = "recurse into subtrees", aliases = { "-r" }) + private boolean recursive; + + @Argument(index = 0, required = true, metaVar = "tree-ish") + private AbstractTreeIterator tree; + + @Override + protected void run() throws Exception { + final TreeWalk walk = new TreeWalk(db); + walk.reset(); // drop the first empty tree, which we do not need here + walk.setRecursive(recursive); + walk.addTree(tree); + + while (walk.next()) { + final FileMode mode = walk.getFileMode(0); + if (mode == FileMode.TREE) + out.print('0'); + out.print(mode); + out.print(' '); + out.print(Constants.typeString(mode.getObjectType())); + + out.print(' '); + out.print(walk.getObjectId(0).name()); + + out.print('\t'); + out.print(walk.getPathString()); + out.println(); + } + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java new file mode 100644 index 000000000..625132ae5 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2006, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.ExampleMode; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.awtui.AwtAuthenticator; +import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.pgm.opt.CmdLineParser; +import org.eclipse.jgit.pgm.opt.SubcommandHandler; +import org.eclipse.jgit.util.HttpSupport; + +/** Command line entry point. */ +public class Main { + @Option(name = "--help", usage = "display this help text", aliases = { "-h" }) + private boolean help; + + @Option(name = "--show-stack-trace", usage = "display the Java stack trace on exceptions") + private boolean showStackTrace; + + @Option(name = "--git-dir", metaVar = "GIT_DIR", usage = "set the git repository to operate on") + private File gitdir; + + @Argument(index = 0, metaVar = "command", required = true, handler = SubcommandHandler.class) + private TextBuiltin subcommand; + + @Argument(index = 1, metaVar = "ARG") + private List arguments = new ArrayList(); + + /** + * Execute the command line. + * + * @param argv + * arguments. + */ + public static void main(final String[] argv) { + final Main me = new Main(); + try { + AwtAuthenticator.install(); + HttpSupport.configureHttpProxy(); + me.execute(argv); + } catch (Die err) { + System.err.println("fatal: " + err.getMessage()); + if (me.showStackTrace) + err.printStackTrace(); + System.exit(128); + } catch (Exception err) { + if (!me.showStackTrace && err.getCause() != null + && err instanceof TransportException) + System.err.println("fatal: " + err.getCause().getMessage()); + + if (err.getClass().getName().startsWith("org.eclipse.jgit.errors.")) { + System.err.println("fatal: " + err.getMessage()); + if (me.showStackTrace) + err.printStackTrace(); + System.exit(128); + } + err.printStackTrace(); + System.exit(1); + } + } + + private void execute(final String[] argv) throws Exception { + final CmdLineParser clp = new CmdLineParser(this); + try { + clp.parseArgument(argv); + } catch (CmdLineException err) { + if (argv.length > 0 && !help) { + System.err.println("fatal: " + err.getMessage()); + System.exit(1); + } + } + + if (argv.length == 0 || help) { + final String ex = clp.printExample(ExampleMode.ALL); + System.err.println("jgit" + ex + " command [ARG ...]"); + if (help) { + System.err.println(); + clp.printUsage(System.err); + System.err.println(); + } else if (subcommand == null) { + System.err.println(); + System.err.println("The most commonly used commands are:"); + final CommandRef[] common = CommandCatalog.common(); + int width = 0; + for (final CommandRef c : common) + width = Math.max(width, c.getName().length()); + width += 2; + + for (final CommandRef c : common) { + System.err.print(' '); + System.err.print(c.getName()); + for (int i = c.getName().length(); i < width; i++) + System.err.print(' '); + System.err.print(c.getUsage()); + System.err.println(); + } + System.err.println(); + } + System.exit(1); + } + + final TextBuiltin cmd = subcommand; + if (cmd.requiresRepository()) { + if (gitdir == null) + gitdir = findGitDir(); + if (gitdir == null || !gitdir.isDirectory()) { + System.err.println("error: can't find git directory"); + System.exit(1); + } + cmd.init(new Repository(gitdir), gitdir); + } else { + cmd.init(null, gitdir); + } + try { + cmd.execute(arguments.toArray(new String[arguments.size()])); + } finally { + if (cmd.out != null) + cmd.out.flush(); + } + } + + private static File findGitDir() { + File current = new File(".").getAbsoluteFile(); + while (current != null) { + final File gitDir = new File(current, ".git"); + if (gitDir.isDirectory()) + return gitDir; + current = current.getParentFile(); + } + return null; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeBase.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeBase.java new file mode 100644 index 000000000..91fd7443b --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MergeBase.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2008, Jonas Fonseca + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import java.util.ArrayList; +import java.util.List; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.filter.RevFilter; + +class MergeBase extends TextBuiltin { + @Option(name = "--all", usage = "display all possible merge bases") + private boolean all; + + @Argument(index = 0, metaVar = "commit-ish", required = true) + void commit_0(final RevCommit c) { + commits.add(c); + } + + @Argument(index = 1, metaVar = "commit-ish", required = true) + private final List commits = new ArrayList(); + + @Override + protected void run() throws Exception { + for (final RevCommit c : commits) + argWalk.markStart(c); + argWalk.setRevFilter(RevFilter.MERGE_BASE); + int max = all ? Integer.MAX_VALUE : 1; + while (max-- > 0) { + final RevCommit b = argWalk.next(); + if (b == null) + break; + out.println(b.getId().name()); + } + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java new file mode 100644 index 000000000..8afa5227d --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.TextProgressMonitor; +import org.eclipse.jgit.transport.PushResult; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.transport.RemoteRefUpdate; +import org.eclipse.jgit.transport.Transport; +import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.transport.RemoteRefUpdate.Status; + +@Command(common = true, usage = "Update remote repository from local refs") +class Push extends TextBuiltin { + @Option(name = "--timeout", metaVar = "SECONDS", usage = "abort connection if no activity") + int timeout = -1; + + @Argument(index = 0, metaVar = "uri-ish") + private String remote = "origin"; + + @Argument(index = 1, metaVar = "refspec") + private final List refSpecs = new ArrayList(); + + @Option(name = "--all") + void addAll(final boolean ignored) { + refSpecs.add(Transport.REFSPEC_PUSH_ALL); + } + + @Option(name = "--tags") + void addTags(final boolean ignored) { + refSpecs.add(Transport.REFSPEC_TAGS); + } + + @Option(name = "--verbose", aliases = { "-v" }) + private boolean verbose = false; + + @Option(name = "--thin") + private boolean thin = Transport.DEFAULT_PUSH_THIN; + + @Option(name = "--no-thin") + void nothin(final boolean ignored) { + thin = false; + } + + @Option(name = "--force", aliases = { "-f" }) + private boolean force; + + @Option(name = "--receive-pack", metaVar = "path") + private String receivePack; + + @Option(name = "--dry-run") + private boolean dryRun; + + private boolean shownURI; + + @Override + protected void run() throws Exception { + if (force) { + final List orig = new ArrayList(refSpecs); + refSpecs.clear(); + for (final RefSpec spec : orig) + refSpecs.add(spec.setForceUpdate(true)); + } + + final List transports; + transports = Transport.openAll(db, remote, Transport.Operation.PUSH); + for (final Transport transport : transports) { + if (0 <= timeout) + transport.setTimeout(timeout); + transport.setPushThin(thin); + if (receivePack != null) + transport.setOptionReceivePack(receivePack); + transport.setDryRun(dryRun); + + final Collection toPush = transport + .findRemoteRefUpdatesFor(refSpecs); + + final URIish uri = transport.getURI(); + final PushResult result; + try { + result = transport.push(new TextProgressMonitor(), toPush); + } finally { + transport.close(); + } + printPushResult(uri, result); + } + } + + private void printPushResult(final URIish uri, + final PushResult result) { + shownURI = false; + boolean everythingUpToDate = true; + + // at first, print up-to-date ones... + for (final RemoteRefUpdate rru : result.getRemoteUpdates()) { + if (rru.getStatus() == Status.UP_TO_DATE) { + if (verbose) + printRefUpdateResult(uri, result, rru); + } else + everythingUpToDate = false; + } + + for (final RemoteRefUpdate rru : result.getRemoteUpdates()) { + // ...then successful updates... + if (rru.getStatus() == Status.OK) + printRefUpdateResult(uri, result, rru); + } + + for (final RemoteRefUpdate rru : result.getRemoteUpdates()) { + // ...finally, others (problematic) + if (rru.getStatus() != Status.OK + && rru.getStatus() != Status.UP_TO_DATE) + printRefUpdateResult(uri, result, rru); + } + + if (everythingUpToDate) + out.println("Everything up-to-date"); + } + + private void printRefUpdateResult(final URIish uri, + final PushResult result, final RemoteRefUpdate rru) { + if (!shownURI) { + shownURI = true; + out.format("To %s\n", uri); + } + + final String remoteName = rru.getRemoteName(); + final String srcRef = rru.isDelete() ? null : rru.getSrcRef(); + + switch (rru.getStatus()) { + case OK: + if (rru.isDelete()) + printUpdateLine('-', "[deleted]", null, remoteName, null); + else { + final Ref oldRef = result.getAdvertisedRef(remoteName); + if (oldRef == null) { + final String summary; + if (remoteName.startsWith(Constants.R_TAGS)) + summary = "[new tag]"; + else + summary = "[new branch]"; + printUpdateLine('*', summary, srcRef, remoteName, null); + } else { + boolean fastForward = rru.isFastForward(); + final char flag = fastForward ? ' ' : '+'; + final String summary = oldRef.getObjectId().abbreviate(db) + .name() + + (fastForward ? ".." : "...") + + rru.getNewObjectId().abbreviate(db).name(); + final String message = fastForward ? null : "forced update"; + printUpdateLine(flag, summary, srcRef, remoteName, message); + } + } + break; + + case NON_EXISTING: + printUpdateLine('X', "[no match]", null, remoteName, null); + break; + + case REJECTED_NODELETE: + printUpdateLine('!', "[rejected]", null, remoteName, + "remote side does not support deleting refs"); + break; + + case REJECTED_NONFASTFORWARD: + printUpdateLine('!', "[rejected]", srcRef, remoteName, + "non-fast forward"); + break; + + case REJECTED_REMOTE_CHANGED: + final String message = "remote ref object changed - is not expected one " + + rru.getExpectedOldObjectId().abbreviate(db).name(); + printUpdateLine('!', "[rejected]", srcRef, remoteName, message); + break; + + case REJECTED_OTHER_REASON: + printUpdateLine('!', "[remote rejected]", srcRef, remoteName, rru + .getMessage()); + break; + + case UP_TO_DATE: + if (verbose) + printUpdateLine('=', "[up to date]", srcRef, remoteName, null); + break; + + case NOT_ATTEMPTED: + case AWAITING_REPORT: + printUpdateLine('?', "[unexpected push-process behavior]", srcRef, + remoteName, rru.getMessage()); + break; + } + } + + private void printUpdateLine(final char flag, final String summary, + final String srcRef, final String destRef, final String message) { + out.format(" %c %-17s", flag, summary); + + if (srcRef != null) + out.format(" %s ->", abbreviateRef(srcRef, true)); + out.format(" %s", abbreviateRef(destRef, true)); + + if (message != null) + out.format(" (%s)", message); + + out.println(); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ReceivePack.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ReceivePack.java new file mode 100644 index 000000000..c6a6fd0cb --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ReceivePack.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2009, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import java.io.File; + +import org.kohsuke.args4j.Argument; +import org.eclipse.jgit.lib.Repository; + +@Command(common = false, usage = "Server side backend for 'jgit push'") +class ReceivePack extends TextBuiltin { + @Argument(index = 0, required = true, metaVar = "DIRECTORY", usage = "Repository to receive into") + File dstGitdir; + + @Override + protected final boolean requiresRepository() { + return false; + } + + @Override + protected void run() throws Exception { + final org.eclipse.jgit.transport.ReceivePack rp; + + if (new File(dstGitdir, ".git").isDirectory()) + dstGitdir = new File(dstGitdir, ".git"); + db = new Repository(dstGitdir); + if (!db.getObjectsDirectory().isDirectory()) + throw die("'" + dstGitdir.getPath() + "' not a git repository"); + rp = new org.eclipse.jgit.transport.ReceivePack(db); + rp.receive(System.in, System.out, System.err); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevList.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevList.java new file mode 100644 index 000000000..1b8c180f9 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevList.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +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.RevTree; + +class RevList extends RevWalkTextBuiltin { + @Override + protected void show(final RevCommit c) throws Exception { + if (c.has(RevFlag.UNINTERESTING)) + out.print('-'); + c.getId().copyTo(outbuffer, out); + if (parents) + for (int i = 0; i < c.getParentCount(); i++) { + out.print(' '); + c.getParent(i).getId().copyTo(outbuffer, out); + } + out.println(); + } + + @Override + protected void show(final ObjectWalk ow, final RevObject obj) + throws Exception { + if (obj.has(RevFlag.UNINTERESTING)) + out.print('-'); + obj.getId().copyTo(outbuffer, out); + final String path = ow.getPathString(); + if (path != null) { + out.print(' '); + out.print(path); + } else if (obj instanceof RevTree) + out.print(' '); + out.println(); + + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java new file mode 100644 index 000000000..98eb2ce86 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2009, Daniel Cheng (aka SDiZ) + * Copyright (C) 2009, Daniel Cheng (aka SDiZ) + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; + +class RevParse extends TextBuiltin { + @Option(name = "--all") + boolean all = false; + + @Argument(index = 0, metaVar = "commit-ish") + private final List commits = new ArrayList(); + + @Override + protected void run() throws Exception { + if (all) { + Map allRefs = db.getAllRefs(); + for (final Ref r : allRefs.values()) + out.println(r.getObjectId().name()); + } else { + for (final ObjectId o : commits) + out.println(o.name()); + } + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java new file mode 100644 index 000000000..cb0cd6d0f --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler; +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.RevSort; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.revwalk.filter.AndRevFilter; +import org.eclipse.jgit.revwalk.filter.AuthorRevFilter; +import org.eclipse.jgit.revwalk.filter.CommitterRevFilter; +import org.eclipse.jgit.revwalk.filter.MessageRevFilter; +import org.eclipse.jgit.revwalk.filter.RevFilter; +import org.eclipse.jgit.treewalk.filter.AndTreeFilter; +import org.eclipse.jgit.treewalk.filter.TreeFilter; + +abstract class RevWalkTextBuiltin extends TextBuiltin { + RevWalk walk; + + @Option(name = "--objects") + boolean objects = false; + + @Option(name = "--parents") + boolean parents = false; + + @Option(name = "--total-count") + boolean count = false; + + char[] outbuffer = new char[Constants.OBJECT_ID_LENGTH * 2]; + + private final EnumSet sorting = EnumSet.noneOf(RevSort.class); + + private void enableRevSort(final RevSort type, final boolean on) { + if (on) + sorting.add(type); + else + sorting.remove(type); + } + + @Option(name = "--date-order") + void enableDateOrder(final boolean on) { + enableRevSort(RevSort.COMMIT_TIME_DESC, on); + } + + @Option(name = "--topo-order") + void enableTopoOrder(final boolean on) { + enableRevSort(RevSort.TOPO, on); + } + + @Option(name = "--reverse") + void enableReverse(final boolean on) { + enableRevSort(RevSort.REVERSE, on); + } + + @Option(name = "--boundary") + void enableBoundary(final boolean on) { + enableRevSort(RevSort.BOUNDARY, on); + } + + @Argument(index = 0, metaVar = "commit-ish") + private final List commits = new ArrayList(); + + @Option(name = "--", metaVar = "path", multiValued = true, handler = PathTreeFilterHandler.class) + private TreeFilter pathFilter = TreeFilter.ALL; + + private final List revLimiter = new ArrayList(); + + @Option(name = "--author") + void addAuthorRevFilter(final String who) { + revLimiter.add(AuthorRevFilter.create(who)); + } + + @Option(name = "--committer") + void addCommitterRevFilter(final String who) { + revLimiter.add(CommitterRevFilter.create(who)); + } + + @Option(name = "--grep") + void addCMessageRevFilter(final String msg) { + revLimiter.add(MessageRevFilter.create(msg)); + } + + @Override + protected void run() throws Exception { + walk = createWalk(); + for (final RevSort s : sorting) + walk.sort(s, true); + + if (pathFilter != TreeFilter.ALL) + walk.setTreeFilter(AndTreeFilter.create(pathFilter, + TreeFilter.ANY_DIFF)); + + if (revLimiter.size() == 1) + walk.setRevFilter(revLimiter.get(0)); + else if (revLimiter.size() > 1) + walk.setRevFilter(AndRevFilter.create(revLimiter)); + + if (commits.isEmpty()) { + final ObjectId head = db.resolve(Constants.HEAD); + if (head == null) + throw die("Cannot resolve " + Constants.HEAD); + commits.add(walk.parseCommit(head)); + } + for (final RevCommit c : commits) { + final RevCommit real = argWalk == walk ? c : walk.parseCommit(c); + if (c.has(RevFlag.UNINTERESTING)) + walk.markUninteresting(real); + else + walk.markStart(real); + } + + final long start = System.currentTimeMillis(); + final int n = walkLoop(); + if (count) { + final long end = System.currentTimeMillis(); + System.err.print(n); + System.err.print(' '); + System.err.print(end - start); + System.err.print(" ms"); + System.err.println(); + } + } + + protected RevWalk createWalk() { + if (objects) + return new ObjectWalk(db); + if (argWalk == null) + argWalk = new RevWalk(db); + return argWalk; + } + + protected int walkLoop() throws Exception { + int n = 0; + for (final RevCommit c : walk) { + n++; + show(c); + } + if (walk instanceof ObjectWalk) { + final ObjectWalk ow = (ObjectWalk) walk; + for (;;) { + final RevObject obj = ow.nextObject(); + if (obj == null) + break; + show(ow, obj); + } + } + return n; + } + + protected abstract void show(final RevCommit c) throws Exception; + + protected void show(final ObjectWalk objectWalk, + final RevObject currentObject) throws Exception { + // Do nothing by default. Most applications cannot show an object. + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java new file mode 100644 index 000000000..63e6e1712 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Rm.java @@ -0,0 +1,98 @@ +/* + * 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.pgm; + +import java.io.File; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import org.kohsuke.args4j.spi.StopOptionHandler; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuildIterator; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.TreeFilter; + +@Command(usage = "Stop tracking a file", common = true) +class Rm extends TextBuiltin { + @Argument(metaVar = "path", usage = "path", multiValued = true, required = true, handler = PathTreeFilterHandler.class) + @Option(name = "--", handler = StopOptionHandler.class) + private TreeFilter paths; + + private File root; + + @Override + protected void run() throws Exception { + root = db.getWorkDir(); + + final DirCache dirc = DirCache.lock(db); + final DirCacheBuilder edit = dirc.builder(); + + final TreeWalk walk = new TreeWalk(db); + walk.reset(); // drop the first empty tree, which we do not need here + walk.setRecursive(true); + walk.setFilter(paths); + walk.addTree(new DirCacheBuildIterator(edit)); + + while (walk.next()) { + final File path = new File(root, walk.getPathString()); + final FileMode mode = walk.getFileMode(0); + if (mode.getObjectType() == Constants.OBJ_BLOB) { + // Deleting a blob is simply a matter of removing + // the file or symlink named by the tree entry. + delete(path); + } + } + + edit.commit(); + } + + private void delete(File p) { + while (p != null && !p.equals(root) && p.delete()) + p = p.getParentFile(); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ShowRef.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ShowRef.java new file mode 100644 index 000000000..7dbb21c5d --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/ShowRef.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2008, Jonas Fonseca + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import java.util.TreeMap; + +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Ref; + +class ShowRef extends TextBuiltin { + @Override + protected void run() throws Exception { + for (final Ref r : new TreeMap(db.getAllRefs()).values()) { + show(r.getObjectId(), r.getName()); + if (r.getPeeledObjectId() != null) + show(r.getPeeledObjectId(), r.getName() + "^{}"); + } + } + + private void show(final AnyObjectId id, final String name) { + out.print(id.name()); + out.print('\t'); + out.print(name); + out.println(); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java new file mode 100644 index 000000000..703b10baf --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Tag.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2008, Charles O'Farrell + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.PersonIdent; + +@Command(common = true, usage = "Create a tag") +class Tag extends TextBuiltin { + @Option(name = "-f", usage = "force replacing an existing tag") + private boolean force; + + @Option(name = "-m", metaVar = "message", usage = "tag message") + private String message = ""; + + @Argument(index = 0, required = true, metaVar = "name") + private String tagName; + + @Argument(index = 1, metaVar = "object") + private ObjectId object; + + @Override + protected void run() throws Exception { + if (object == null) { + object = db.resolve(Constants.HEAD); + if (object == null) + throw die("Cannot resolve " + Constants.HEAD); + } + + if (!tagName.startsWith(Constants.R_TAGS)) + tagName = Constants.R_TAGS + tagName; + if (!force && db.resolve(tagName) != null) { + throw die("fatal: tag '" + + tagName.substring(Constants.R_TAGS.length()) + + "' exists"); + } + + final ObjectLoader ldr = db.openObject(object); + if (ldr == null) + throw new MissingObjectException(object, "any"); + + org.eclipse.jgit.lib.Tag tag = new org.eclipse.jgit.lib.Tag(db); + tag.setObjId(object); + tag.setType(Constants.typeString(ldr.getType())); + tag.setTagger(new PersonIdent(db)); + tag.setMessage(message.replaceAll("\r", "")); + tag.setTag(tagName.substring(Constants.R_TAGS.length())); + tag.tag(); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java new file mode 100644 index 000000000..edd4fbcf9 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/TextBuiltin.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +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.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; + +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.pgm.opt.CmdLineParser; +import org.eclipse.jgit.revwalk.RevWalk; + +/** + * Abstract command which can be invoked from the command line. + *

+ * Commands are configured with a single "current" repository and then the + * {@link #execute(String[])} method is invoked with the arguments that appear + * on the command line after the command name. + *

+ * Command constructors should perform as little work as possible as they may be + * invoked very early during process loading, and the command may not execute + * even though it was constructed. + */ +public abstract class TextBuiltin { + private String commandName; + + @Option(name = "--help", usage = "display this help text", aliases = { "-h" }) + private boolean help; + + /** Stream to output to, typically this is standard output. */ + protected PrintWriter out; + + /** Git repository the command was invoked within. */ + protected Repository db; + + /** Directory supplied via --git-dir command line option. */ + protected File gitdir; + + /** RevWalk used during command line parsing, if it was required. */ + protected RevWalk argWalk; + + final void setCommandName(final String name) { + commandName = name; + } + + /** @return true if {@link #db}/{@link #getRepository()} is required. */ + protected boolean requiresRepository() { + return true; + } + + void init(final Repository repo, final File gd) { + try { + final String outputEncoding = repo != null ? repo.getConfig() + .getString("i18n", null, "logOutputEncoding") : null; + if (outputEncoding != null) + out = new PrintWriter(new BufferedWriter( + new OutputStreamWriter(System.out, outputEncoding))); + else + out = new PrintWriter(new BufferedWriter( + new OutputStreamWriter(System.out))); + } catch (IOException e) { + throw die("cannot create output stream"); + } + + if (repo != null) { + db = repo; + gitdir = repo.getDirectory(); + } else { + db = null; + gitdir = gd; + } + } + + /** + * Parse arguments and run this command. + * + * @param args + * command line arguments passed after the command name. + * @throws Exception + * an error occurred while processing the command. The main + * framework will catch the exception and print a message on + * standard error. + */ + public final void execute(String[] args) throws Exception { + parseArguments(args); + run(); + } + + /** + * Parses the command line arguments prior to running. + *

+ * This method should only be invoked by {@link #execute(String[])}, prior + * to calling {@link #run()}. The default implementation parses all + * arguments into this object's instance fields. + * + * @param args + * the arguments supplied on the command line, if any. + */ + protected void parseArguments(final String[] args) { + final CmdLineParser clp = new CmdLineParser(this); + try { + clp.parseArgument(args); + } catch (CmdLineException err) { + if (!help) { + System.err.println("fatal: " + err.getMessage()); + System.exit(1); + } + } + + if (help) { + printUsageAndExit(clp); + } + + argWalk = clp.getRevWalkGently(); + } + + /** + * Print the usage line + * + * @param clp + */ + public void printUsageAndExit(final CmdLineParser clp) { + printUsageAndExit("", clp); + } + + /** + * Print an error message and the usage line + * + * @param message + * @param clp + */ + public void printUsageAndExit(final String message, final CmdLineParser clp) { + System.err.println(message); + System.err.print("jgit "); + System.err.print(commandName); + clp.printSingleLineUsage(System.err); + System.err.println(); + + System.err.println(); + clp.printUsage(System.err); + System.err.println(); + + System.exit(1); + } + + /** + * Perform the actions of this command. + *

+ * This method should only be invoked by {@link #execute(String[])}. + * + * @throws Exception + * an error occurred while processing the command. The main + * framework will catch the exception and print a message on + * standard error. + */ + protected abstract void run() throws Exception; + + /** + * @return the repository this command accesses. + */ + public Repository getRepository() { + return db; + } + + ObjectId resolve(final String s) throws IOException { + final ObjectId r = db.resolve(s); + if (r == null) + throw die("Not a revision: " + s); + return r; + } + + /** + * @param why + * textual explanation + * @return a runtime exception the caller is expected to throw + */ + protected static Die die(final String why) { + return new Die(why); + } + + String abbreviateRef(String dst, boolean abbreviateRemote) { + if (dst.startsWith(R_HEADS)) + dst = dst.substring(R_HEADS.length()); + else if (dst.startsWith(R_TAGS)) + dst = dst.substring(R_TAGS.length()); + else if (abbreviateRemote && dst.startsWith(R_REMOTES)) + dst = dst.substring(R_REMOTES.length()); + return dst; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/UploadPack.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/UploadPack.java new file mode 100644 index 000000000..85dbbc5d9 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/UploadPack.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2009, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm; + +import java.io.File; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.lib.Repository; + +@Command(common = false, usage = "Server side backend for 'jgit fetch'") +class UploadPack extends TextBuiltin { + @Option(name = "--timeout", metaVar = "SECONDS", usage = "abort connection if no activity") + int timeout = -1; + + @Argument(index = 0, required = true, metaVar = "DIRECTORY", usage = "Repository to read from") + File srcGitdir; + + @Override + protected final boolean requiresRepository() { + return false; + } + + @Override + protected void run() throws Exception { + final org.eclipse.jgit.transport.UploadPack rp; + + if (new File(srcGitdir, ".git").isDirectory()) + srcGitdir = new File(srcGitdir, ".git"); + db = new Repository(srcGitdir); + if (!db.getObjectsDirectory().isDirectory()) + throw die("'" + srcGitdir.getPath() + "' not a git repository"); + rp = new org.eclipse.jgit.transport.UploadPack(db); + if (0 <= timeout) + rp.setTimeout(timeout); + rp.upload(System.in, System.out, System.err); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Version.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Version.java new file mode 100644 index 000000000..11b6e8352 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Version.java @@ -0,0 +1,58 @@ +/* + * 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.pgm; + +@Command(common = true, usage = "Display the version of jgit") +class Version extends TextBuiltin { + @Override + protected void run() throws Exception { + final Package pkg = getClass().getPackage(); + if (pkg == null || pkg.getImplementationVersion() == null) + throw die("Cannot read package information."); + + out.print("jgit version "); + out.print(pkg.getImplementationVersion()); + out.println(); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/build/JarLinkUtil.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/build/JarLinkUtil.java new file mode 100644 index 000000000..e63f39c01 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/build/JarLinkUtil.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm.build; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.Option; +import org.kohsuke.args4j.spi.MapOptionHandler; + +/** + * Combines multiple JAR and directory sources into a single JAR file. + *

+ * This is a crude command line utility to combine multiple JAR files into a + * single JAR file, without first needing to unpack the individual JARs. + *

+ * The output ZIP stream is sent to standard out and can be redirected onto the + * end of a shell script which starts the JRE. + */ +public class JarLinkUtil { + /** + * Combine multiple JARs. + * + * @param argv + * the command line arguments indicating the files to pack. + * @throws IOException + * a source file could not be read. + */ + public static void main(final String[] argv) throws IOException { + final JarLinkUtil util = new JarLinkUtil(); + final CmdLineParser clp = new CmdLineParser(util); + try { + clp.parseArgument(argv); + } catch (CmdLineException e) { + clp.printSingleLineUsage(System.err); + System.exit(1); + } + util.run(); + } + + @Option(name = "-include", required = true) + private List includes = new ArrayList(); + + @Option(name = "-file", handler = MapOptionHandler.class) + private Map files = new HashMap(); + + private final Map chosenSources = new HashMap(); + + private long creationTime; + + private ZipOutputStream zos; + + private JarLinkUtil() { + // Command line utility only. + } + + private void run() throws IOException { + for (final File src : includes) { + if (src.isFile()) + scanJar(src); + else + scanDirectory(src, src, ""); + } + for (final Map.Entry e : files.entrySet()) + chosenSources.put(e.getKey(), new File(e.getValue())); + + creationTime = System.currentTimeMillis(); + zos = new ZipOutputStream(System.out); + zos.setLevel(9); + + for (final File src : includes) { + if (src.isFile()) + appendJar(src); + else + appendDirectory(src, src, ""); + } + for (final String name : files.keySet()) + appendFile(chosenSources.get(name), name); + + zos.close(); + } + + private void scanJar(final File jarPath) throws IOException { + final ZipFile zf = new ZipFile(jarPath); + final Enumeration e = zf.entries(); + while (e.hasMoreElements()) + chosenSources.put(e.nextElement().getName(), jarPath); + zf.close(); + } + + private void scanDirectory(final File rootPath, final File dirPath, + final String pfx) throws IOException { + final File[] entries = dirPath.listFiles(); + if (entries == null) + return; + for (final File e : entries) { + if (e.getName().equals(".") || e.getName().equals("..")) + continue; + + if (e.isDirectory()) + scanDirectory(rootPath, e, pfx + e.getName() + "/"); + else + chosenSources.put(pfx + e.getName(), rootPath); + } + } + + private void appendJar(final File jarPath) throws IOException { + final ZipFile zf = new ZipFile(jarPath); + final Enumeration e = zf.entries(); + while (e.hasMoreElements()) { + final ZipEntry ze = e.nextElement(); + final String name = ze.getName(); + if (chosenSources.get(name) == jarPath) + appendEntry(name, ze.getSize(), ze.getTime(), zf + .getInputStream(ze)); + } + zf.close(); + } + + private void appendDirectory(final File rootDir, final File dirPath, + final String pfx) throws IOException { + final File[] entries = dirPath.listFiles(); + if (entries == null) + return; + for (final File e : entries) { + if (e.getName().equals(".") || e.getName().equals("..")) + continue; + + if (e.isDirectory()) + appendDirectory(rootDir, e, pfx + e.getName() + "/"); + else if (chosenSources.get(pfx + e.getName()) == rootDir) + appendFile(e, pfx + e.getName()); + } + } + + private void appendFile(final File path, final String name) + throws IOException { + final long len = path.length(); + final InputStream is = new FileInputStream(path); + appendEntry(name, len, creationTime, is); + } + + private void appendEntry(final String name, final long len, + final long time, final InputStream is) throws IOException { + final ZipEntry ze = new ZipEntry(name); + ze.setSize(len); + ze.setTime(time); + zos.putNextEntry(ze); + try { + final byte[] buf = new byte[4096]; + int n; + while ((n = is.read(buf)) >= 0) + zos.write(buf, 0, n); + } finally { + is.close(); + } + zos.closeEntry(); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/MakeCacheTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/MakeCacheTree.java new file mode 100644 index 000000000..8949cbce6 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/MakeCacheTree.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm.debug; + +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheTree; +import org.eclipse.jgit.pgm.TextBuiltin; + +class MakeCacheTree extends TextBuiltin { + @Override + protected void run() throws Exception { + final DirCache cache = DirCache.read(db); + final DirCacheTree tree = cache.getCacheTree(true); + show(tree); + } + + private void show(final DirCacheTree tree) { + out.print("\""); + out.print(tree.getPathString()); + out.print("\""); + out.print(": "); + out.print(tree.getEntrySpan()); + out.print(" entries"); + out.print(", "); + out.print(tree.getChildCount()); + out.print(" children"); + out.println(); + + for (int i = 0; i < tree.getChildCount(); i++) + show(tree.getChild(i)); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadDirCache.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadDirCache.java new file mode 100644 index 000000000..b0c1c77fd --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ReadDirCache.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2008, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm.debug; + +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.pgm.TextBuiltin; + +class ReadDirCache extends TextBuiltin { + @Override + protected void run() throws Exception { + final int cnt = 100; + final long start = System.currentTimeMillis(); + for (int i = 0; i < cnt; i++) + DirCache.read(db); + final long end = System.currentTimeMillis(); + out.println(" average " + ((end - start) / cnt) + " ms/read"); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java new file mode 100644 index 000000000..50b889849 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java @@ -0,0 +1,313 @@ +/* + * 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.pgm.debug; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.ObjectWritingException; +import org.eclipse.jgit.lib.Commit; +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.PersonIdent; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.RefWriter; +import org.eclipse.jgit.lib.TextProgressMonitor; +import org.eclipse.jgit.lib.Tree; +import org.eclipse.jgit.pgm.TextBuiltin; +import org.eclipse.jgit.revwalk.RevWalk; + +/** + * Recreates a repository from another one's commit graph. + *

+ * Do not run this on a repository unless you want to destroy it. + *

+ * To create the input files, in the source repository use: + * + *

+ * git for-each-ref >in.refs
+ * git log --all '--pretty=format:%H %ct %P' >in.dag
+ * 
+ *

+ * Run the rebuild in either an empty repository, or a clone of the source. Any + * missing commits (which might be the entire graph) will be created. All refs + * will be modified to match the input exactly, which means some refs may be + * deleted from the current repository. + *

+ */ +class RebuildCommitGraph extends TextBuiltin { + private final String REALLY = "--destroy-this-repository"; + + @Option(name = REALLY, usage = "approve destruction of repository") + boolean really; + + @Argument(index = 0, required = true, metaVar = "REFS", usage = "for-each-ref output") + File refList; + + @Argument(index = 1, required = true, metaVar = "DAG", usage = "log --all '--pretty=format:%H %ct %P' output") + File graph; + + private final ProgressMonitor pm = new TextProgressMonitor(); + + private Map rewrites = new HashMap(); + + @Override + protected void run() throws Exception { + if (!really && !db.getAllRefs().isEmpty()) { + final StringBuilder m = new StringBuilder(); + m.append("fatal: "); + m.append("This program will destroy the repository:"); + m.append("\n"); + m.append("fatal:\n"); + m.append("fatal: "); + m.append(db.getDirectory().getAbsolutePath()); + m.append("\n"); + m.append("fatal:\n"); + m.append("fatal: "); + m.append("To continue, add "); + m.append(REALLY); + m.append(" to the command line"); + m.append("\n"); + m.append("fatal:"); + System.err.println(m); + throw die("Need approval to destroy current repository"); + } + if (!refList.isFile()) + throw die("no such file: " + refList.getPath()); + if (!graph.isFile()) + throw die("no such file: " + graph.getPath()); + + recreateCommitGraph(); + detachHead(); + deleteAllRefs(); + recreateRefs(); + } + + private void recreateCommitGraph() throws IOException { + final RevWalk rw = new RevWalk(db); + final Map toRewrite = new HashMap(); + List queue = new ArrayList(); + final BufferedReader br = new BufferedReader(new InputStreamReader( + new FileInputStream(graph), Constants.CHARSET)); + try { + String line; + while ((line = br.readLine()) != null) { + final String[] parts = line.split("[ \t]{1,}"); + final ObjectId oldId = ObjectId.fromString(parts[0]); + try { + rw.parseCommit(oldId); + // We have it already. Don't rewrite it. + continue; + } catch (MissingObjectException mue) { + // Fall through and rewrite it. + } + + final long time = Long.parseLong(parts[1]) * 1000L; + final ObjectId[] parents = new ObjectId[parts.length - 2]; + for (int i = 0; i < parents.length; i++) { + parents[i] = ObjectId.fromString(parts[2 + i]); + } + + final ToRewrite t = new ToRewrite(oldId, time, parents); + toRewrite.put(oldId, t); + queue.add(t); + } + } finally { + br.close(); + } + + pm.beginTask("Rewriting commits", queue.size()); + final ObjectWriter ow = new ObjectWriter(db); + final ObjectId emptyTree = ow.writeTree(new Tree(db)); + final PersonIdent me = new PersonIdent("jgit rebuild-commitgraph", + "rebuild-commitgraph@localhost"); + while (!queue.isEmpty()) { + final ListIterator itr = queue + .listIterator(queue.size()); + queue = new ArrayList(); + REWRITE: while (itr.hasPrevious()) { + final ToRewrite t = itr.previous(); + final ObjectId[] newParents = new ObjectId[t.oldParents.length]; + for (int k = 0; k < t.oldParents.length; k++) { + final ToRewrite p = toRewrite.get(t.oldParents[k]); + if (p != null) { + if (p.newId == null) { + // Must defer until after the parent is rewritten. + queue.add(t); + continue REWRITE; + } else { + newParents[k] = p.newId; + } + } else { + // We have the old parent object. Use it. + // + newParents[k] = t.oldParents[k]; + } + } + + final Commit newc = new Commit(db); + newc.setTreeId(emptyTree); + newc.setAuthor(new PersonIdent(me, new Date(t.commitTime))); + newc.setCommitter(newc.getAuthor()); + newc.setParentIds(newParents); + newc.setMessage("ORIGINAL " + t.oldId.name() + "\n"); + t.newId = ow.writeCommit(newc); + rewrites.put(t.oldId, t.newId); + pm.update(1); + } + } + pm.endTask(); + } + + private static class ToRewrite { + final ObjectId oldId; + + final long commitTime; + + final ObjectId[] oldParents; + + ObjectId newId; + + ToRewrite(final ObjectId o, final long t, final ObjectId[] p) { + oldId = o; + commitTime = t; + oldParents = p; + } + } + + private void detachHead() throws IOException { + final String head = db.getFullBranch(); + final ObjectId id = db.resolve(Constants.HEAD); + if (!ObjectId.isId(head) && id != null) { + final LockFile lf; + lf = new LockFile(new File(db.getDirectory(), Constants.HEAD)); + if (!lf.lock()) + throw new IOException("Cannot lock HEAD"); + lf.write(id); + if (!lf.commit()) + throw new IOException("Cannot deatch HEAD"); + } + } + + private void deleteAllRefs() throws Exception { + final RevWalk rw = new RevWalk(db); + for (final Ref r : db.getAllRefs().values()) { + if (Constants.HEAD.equals(r.getName())) + continue; + final RefUpdate u = db.updateRef(r.getName()); + u.setForceUpdate(true); + u.delete(rw); + } + } + + private void recreateRefs() throws Exception { + final Map refs = computeNewRefs(); + new RefWriter(refs.values()) { + @Override + protected void writeFile(final String name, final byte[] content) + throws IOException { + final File file = new File(db.getDirectory(), name); + final LockFile lck = new LockFile(file); + if (!lck.lock()) + throw new ObjectWritingException("Can't write " + file); + try { + lck.write(content); + } catch (IOException ioe) { + throw new ObjectWritingException("Can't write " + file); + } + if (!lck.commit()) + throw new ObjectWritingException("Can't write " + file); + } + }.writePackedRefs(); + } + + private Map computeNewRefs() throws IOException { + final RevWalk rw = new RevWalk(db); + final Map refs = new HashMap(); + final BufferedReader br = new BufferedReader(new InputStreamReader( + new FileInputStream(refList), Constants.CHARSET)); + try { + String line; + while ((line = br.readLine()) != null) { + final String[] parts = line.split("[ \t]{1,}"); + final ObjectId origId = ObjectId.fromString(parts[0]); + final String type = parts[1]; + final String name = parts[2]; + + ObjectId id = rewrites.get(origId); + if (id == null) + id = origId; + try { + rw.parseAny(id); + } catch (MissingObjectException mue) { + if (!Constants.TYPE_COMMIT.equals(type)) { + System.err.println("skipping " + type + " " + name); + continue; + } + throw new MissingObjectException(id, type); + } + refs.put(name, new Ref(Ref.Storage.PACKED, name, id)); + } + } finally { + br.close(); + } + return refs; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCacheTree.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCacheTree.java new file mode 100644 index 000000000..29cdc98a8 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCacheTree.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2008, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm.debug; + +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheTree; +import org.eclipse.jgit.pgm.TextBuiltin; + +class ShowCacheTree extends TextBuiltin { + @Override + protected void run() throws Exception { + final DirCache cache = DirCache.read(db); + final DirCacheTree tree = cache.getCacheTree(false); + if (tree == null) + throw die("no 'TREE' section in index"); + show(tree); + } + + private void show(final DirCacheTree tree) { + out.print("\""); + out.print(tree.getPathString()); + out.print("\""); + out.print(": "); + out.print(tree.getEntrySpan()); + out.print(" entries"); + out.print(", "); + out.print(tree.getChildCount()); + out.print(" children"); + out.println(); + + for (int i = 0; i < tree.getChildCount(); i++) + show(tree.getChild(i)); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCommands.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCommands.java new file mode 100644 index 000000000..063cab5ac --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowCommands.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm.debug; + +import java.net.URL; + +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.pgm.Command; +import org.eclipse.jgit.pgm.CommandCatalog; +import org.eclipse.jgit.pgm.CommandRef; +import org.eclipse.jgit.pgm.TextBuiltin; + +@Command(usage = "Display a list of all registered jgit commands") +class ShowCommands extends TextBuiltin { + @Option(name = "--pretty", usage = "alter the detail shown") + private Format pretty = Format.USAGE; + + @Override + protected void run() throws Exception { + final CommandRef[] list = CommandCatalog.all(); + + int width = 0; + for (final CommandRef c : list) + width = Math.max(width, c.getName().length()); + width += 2; + + for (final CommandRef c : list) { + System.err.print(c.isCommon() ? '*' : ' '); + System.err.print(' '); + + System.err.print(c.getName()); + for (int i = c.getName().length(); i < width; i++) + System.err.print(' '); + + pretty.print(c); + System.err.println(); + } + System.err.println(); + } + + static enum Format { + /** */ + USAGE { + void print(final CommandRef c) { + System.err.print(c.getUsage()); + } + }, + + /** */ + CLASSES { + void print(final CommandRef c) { + System.err.print(c.getImplementationClassName()); + } + }, + + /** */ + URLS { + void print(final CommandRef c) { + final ClassLoader ldr = c.getImplementationClassLoader(); + + String cn = c.getImplementationClassName(); + cn = cn.replace('.', '/') + ".class"; + + final URL url = ldr.getResource(cn); + if (url == null) { + System.err.print("!! NOT FOUND !!"); + return; + } + + String rn = url.toExternalForm(); + if (rn.endsWith(cn)) + rn = rn.substring(0, rn.length() - cn.length()); + + System.err.print(rn); + } + }; + + abstract void print(CommandRef c); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java new file mode 100644 index 000000000..854596c97 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/ShowDirCache.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2008, Google Inc. + * Copyright (C) 2008, Jonas Fonseca + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm.debug; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.pgm.TextBuiltin; + +class ShowDirCache extends TextBuiltin { + @Override + protected void run() throws Exception { + final SimpleDateFormat fmt; + fmt = new SimpleDateFormat("yyyyMMdd,HHmmss.SSS"); + + final DirCache cache = DirCache.read(db); + for (int i = 0; i < cache.getEntryCount(); i++) { + final DirCacheEntry ent = cache.getEntry(i); + final FileMode mode = FileMode.fromBits(ent.getRawMode()); + final int len = ent.getLength(); + final Date mtime = new Date(ent.getLastModified()); + + out.print(mode); + out.format(" %6d", len); + out.print(' '); + out.print(fmt.format(mtime)); + out.print(' '); + out.print(ent.getObjectId().name()); + out.print('\t'); + out.print(ent.getPathString()); + out.println(); + } + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteDirCache.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteDirCache.java new file mode 100644 index 000000000..54301dd04 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/WriteDirCache.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2008, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm.debug; + +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.pgm.TextBuiltin; + +class WriteDirCache extends TextBuiltin { + @Override + protected void run() throws Exception { + final DirCache cache = DirCache.read(db); + if (!cache.lock()) + throw die("failed to lock index"); + cache.read(); + cache.write(); + if (!cache.commit()) + throw die("failed to commit index"); + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java new file mode 100644 index 000000000..c31676d72 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/AbstractTreeIteratorHandler.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm.opt; + +import java.io.File; +import java.io.IOException; + +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.OptionDef; +import org.kohsuke.args4j.spi.OptionHandler; +import org.kohsuke.args4j.spi.Parameters; +import org.kohsuke.args4j.spi.Setter; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheIterator; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.WindowCursor; +import org.eclipse.jgit.treewalk.AbstractTreeIterator; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.eclipse.jgit.treewalk.FileTreeIterator; + +/** + * Custom argument handler {@link AbstractTreeIterator} from string values. + *

+ * Assumes the parser has been initialized with a Repository. + */ +public class AbstractTreeIteratorHandler extends + OptionHandler { + private final org.eclipse.jgit.pgm.opt.CmdLineParser clp; + + /** + * Create a new handler for the command name. + *

+ * This constructor is used only by args4j. + * + * @param parser + * @param option + * @param setter + */ + public AbstractTreeIteratorHandler(final CmdLineParser parser, + final OptionDef option, + final Setter setter) { + super(parser, option, setter); + clp = (org.eclipse.jgit.pgm.opt.CmdLineParser) parser; + } + + @Override + public int parseArguments(final Parameters params) throws CmdLineException { + final String name = params.getParameter(0); + + if (new File(name).isDirectory()) { + setter.addValue(new FileTreeIterator(new File(name))); + return 1; + } + + if (new File(name).isFile()) { + final DirCache dirc; + try { + dirc = DirCache.read(new File(name)); + } catch (IOException e) { + throw new CmdLineException(name + " is not an index file", e); + } + setter.addValue(new DirCacheIterator(dirc)); + return 1; + } + + final ObjectId id; + try { + id = clp.getRepository().resolve(name); + } catch (IOException e) { + throw new CmdLineException(e.getMessage()); + } + if (id == null) + throw new CmdLineException(name + " is not a tree"); + + final CanonicalTreeParser p = new CanonicalTreeParser(); + final WindowCursor curs = new WindowCursor(); + try { + p.reset(clp.getRepository(), clp.getRevWalk().parseTree(id), curs); + } catch (MissingObjectException e) { + throw new CmdLineException(name + " is not a tree"); + } catch (IncorrectObjectTypeException e) { + throw new CmdLineException(name + " is not a tree"); + } catch (IOException e) { + throw new CmdLineException("cannot read " + name + ": " + + e.getMessage()); + } finally { + curs.release(); + } + + setter.addValue(p); + return 1; + } + + @Override + public String getDefaultMetaVariable() { + return "tree-ish"; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java new file mode 100644 index 000000000..a126fb1d8 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/CmdLineParser.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm.opt; + +import java.util.ArrayList; + +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.IllegalAnnotationError; +import org.kohsuke.args4j.Option; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.pgm.TextBuiltin; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTree; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.treewalk.AbstractTreeIterator; + +/** + * Extended command line parser which handles --foo=value arguments. + *

+ * The args4j package does not natively handle --foo=value and instead prefers + * to see --foo value on the command line. Many users are used to the GNU style + * --foo=value long option, so we convert from the GNU style format to the + * args4j style format prior to invoking args4j for parsing. + */ +public class CmdLineParser extends org.kohsuke.args4j.CmdLineParser { + static { + registerHandler(AbstractTreeIterator.class, + AbstractTreeIteratorHandler.class); + registerHandler(ObjectId.class, ObjectIdHandler.class); + registerHandler(RefSpec.class, RefSpecHandler.class); + registerHandler(RevCommit.class, RevCommitHandler.class); + registerHandler(RevTree.class, RevTreeHandler.class); + } + + private final Repository db; + + private RevWalk walk; + + /** + * Creates a new command line owner that parses arguments/options and set + * them into the given object. + * + * @param bean + * instance of a class annotated by {@link Option} and + * {@link Argument}. this object will receive values. + * + * @throws IllegalAnnotationError + * if the option bean class is using args4j annotations + * incorrectly. + */ + public CmdLineParser(final Object bean) { + this(bean, null); + } + + /** + * Creates a new command line owner that parses arguments/options and set + * them into the given object. + * + * @param bean + * instance of a class annotated by {@link Option} and + * {@link Argument}. this object will receive values. + * @param repo + * repository this parser can translate options through. + * @throws IllegalAnnotationError + * if the option bean class is using args4j annotations + * incorrectly. + */ + public CmdLineParser(final Object bean, Repository repo) { + super(bean); + if (repo == null && bean instanceof TextBuiltin) + repo = ((TextBuiltin) bean).getRepository(); + this.db = repo; + } + + @Override + public void parseArgument(final String... args) throws CmdLineException { + final ArrayList tmp = new ArrayList(args.length); + for (int argi = 0; argi < args.length; argi++) { + final String str = args[argi]; + if (str.equals("--")) { + while (argi < args.length) + tmp.add(args[argi++]); + break; + } + + if (str.startsWith("--")) { + final int eq = str.indexOf('='); + if (eq > 0) { + tmp.add(str.substring(0, eq)); + tmp.add(str.substring(eq + 1)); + continue; + } + } + + tmp.add(str); + } + + super.parseArgument(tmp.toArray(new String[tmp.size()])); + } + + /** + * Get the repository this parser translates values through. + * + * @return the repository, if specified during construction. + */ + public Repository getRepository() { + if (db == null) + throw new IllegalStateException("No Git repository configured."); + return db; + } + + /** + * Get the revision walker used to support option parsing. + * + * @return the revision walk used by this option parser. + */ + public RevWalk getRevWalk() { + if (walk == null) + walk = new RevWalk(getRepository()); + return walk; + } + + /** + * Get the revision walker used to support option parsing. + *

+ * This method does not initialize the RevWalk and may return null. + * + * @return the revision walk used by this option parser, or null. + */ + public RevWalk getRevWalkGently() { + return walk; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java new file mode 100644 index 000000000..d3f460c89 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/ObjectIdHandler.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm.opt; + +import java.io.IOException; + +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.OptionDef; +import org.kohsuke.args4j.spi.OptionHandler; +import org.kohsuke.args4j.spi.Parameters; +import org.kohsuke.args4j.spi.Setter; +import org.eclipse.jgit.lib.ObjectId; + +/** + * Custom argument handler {@link ObjectId} from string values. + *

+ * Assumes the parser has been initialized with a Repository. + */ +public class ObjectIdHandler extends OptionHandler { + private final org.eclipse.jgit.pgm.opt.CmdLineParser clp; + + /** + * Create a new handler for the command name. + *

+ * This constructor is used only by args4j. + * + * @param parser + * @param option + * @param setter + */ + public ObjectIdHandler(final CmdLineParser parser, final OptionDef option, + final Setter setter) { + super(parser, option, setter); + clp = (org.eclipse.jgit.pgm.opt.CmdLineParser) parser; + } + + @Override + public int parseArguments(final Parameters params) throws CmdLineException { + final String name = params.getParameter(0); + final ObjectId id; + try { + id = clp.getRepository().resolve(name); + } catch (IOException e) { + throw new CmdLineException(e.getMessage()); + } + if (id != null) { + setter.addValue(id); + return 1; + } + + throw new CmdLineException(name + " is not an object"); + } + + @Override + public String getDefaultMetaVariable() { + return "object"; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/PathTreeFilterHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/PathTreeFilterHandler.java new file mode 100644 index 000000000..bebf3d9a7 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/PathTreeFilterHandler.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm.opt; + +import java.util.ArrayList; +import java.util.List; + +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.Option; +import org.kohsuke.args4j.OptionDef; +import org.kohsuke.args4j.spi.OptionHandler; +import org.kohsuke.args4j.spi.Parameters; +import org.kohsuke.args4j.spi.Setter; +import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; +import org.eclipse.jgit.treewalk.filter.TreeFilter; + +/** + * Create a {@link TreeFilter} to patch math names. + *

+ * This handler consumes all arguments to the end of the command line, and is + * meant to be used on an {@link Option} of name "--". + */ +public class PathTreeFilterHandler extends OptionHandler { + /** + * Create a new handler for the command name. + *

+ * This constructor is used only by args4j. + * + * @param parser + * @param option + * @param setter + */ + public PathTreeFilterHandler(final CmdLineParser parser, + final OptionDef option, final Setter setter) { + super(parser, option, setter); + } + + @Override + public int parseArguments(final Parameters params) throws CmdLineException { + final List filters = new ArrayList(); + for (int idx = 0;; idx++) { + final String path; + try { + path = params.getParameter(idx); + } catch (CmdLineException cle) { + break; + } + filters.add(PathFilter.create(path)); + } + + if (filters.size() == 0) + return 0; + if (filters.size() == 1) { + setter.addValue(filters.get(0)); + return 1; + } + setter.addValue(PathFilterGroup.create(filters)); + return filters.size(); + } + + @Override + public String getDefaultMetaVariable() { + return "path ..."; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RefSpecHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RefSpecHandler.java new file mode 100644 index 000000000..133c5f8db --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RefSpecHandler.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm.opt; + +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.OptionDef; +import org.kohsuke.args4j.spi.OptionHandler; +import org.kohsuke.args4j.spi.Parameters; +import org.kohsuke.args4j.spi.Setter; +import org.eclipse.jgit.transport.RefSpec; + +/** + * Custom argument handler {@link RefSpec} from string values. + *

+ * Assumes the parser has been initialized with a Repository. + */ +public class RefSpecHandler extends OptionHandler { + /** + * Create a new handler for the command name. + *

+ * This constructor is used only by args4j. + * + * @param parser + * @param option + * @param setter + */ + public RefSpecHandler(final CmdLineParser parser, final OptionDef option, + final Setter setter) { + super(parser, option, setter); + } + + @Override + public int parseArguments(final Parameters params) throws CmdLineException { + setter.addValue(new RefSpec(params.getParameter(0))); + return 1; + } + + @Override + public String getDefaultMetaVariable() { + return "refspec"; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java new file mode 100644 index 000000000..01caaf201 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevCommitHandler.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm.opt; + +import java.io.IOException; + +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.OptionDef; +import org.kohsuke.args4j.spi.OptionHandler; +import org.kohsuke.args4j.spi.Parameters; +import org.kohsuke.args4j.spi.Setter; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevFlag; + +/** + * Custom argument handler {@link RevCommit} from string values. + *

+ * Assumes the parser has been initialized with a Repository. + */ +public class RevCommitHandler extends OptionHandler { + private final org.eclipse.jgit.pgm.opt.CmdLineParser clp; + + /** + * Create a new handler for the command name. + *

+ * This constructor is used only by args4j. + * + * @param parser + * @param option + * @param setter + */ + public RevCommitHandler(final CmdLineParser parser, final OptionDef option, + final Setter setter) { + super(parser, option, setter); + clp = (org.eclipse.jgit.pgm.opt.CmdLineParser) parser; + } + + @Override + public int parseArguments(final Parameters params) throws CmdLineException { + String name = params.getParameter(0); + + boolean interesting = true; + if (name.startsWith("^")) { + name = name.substring(1); + interesting = false; + } + + final int dot2 = name.indexOf(".."); + if (dot2 != -1) { + if (!option.isMultiValued()) + throw new CmdLineException("Only one " + option.metaVar() + + " expected in " + name + "." + ""); + + final String left = name.substring(0, dot2); + final String right = name.substring(dot2 + 2); + addOne(left, false); + addOne(right, true); + return 1; + } + + addOne(name, interesting); + return 1; + } + + private void addOne(final String name, final boolean interesting) + throws CmdLineException { + final ObjectId id; + try { + id = clp.getRepository().resolve(name); + } catch (IOException e) { + throw new CmdLineException(e.getMessage()); + } + if (id == null) + throw new CmdLineException(name + " is not a commit"); + + final RevCommit c; + try { + c = clp.getRevWalk().parseCommit(id); + } catch (MissingObjectException e) { + throw new CmdLineException(name + " is not a commit"); + } catch (IncorrectObjectTypeException e) { + throw new CmdLineException(name + " is not a commit"); + } catch (IOException e) { + throw new CmdLineException("cannot read " + name + ": " + + e.getMessage()); + } + + if (interesting) + c.remove(RevFlag.UNINTERESTING); + else + c.add(RevFlag.UNINTERESTING); + + setter.addValue(c); + } + + @Override + public String getDefaultMetaVariable() { + return "commit-ish"; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java new file mode 100644 index 000000000..c564b9b01 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/RevTreeHandler.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm.opt; + +import java.io.IOException; + +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.OptionDef; +import org.kohsuke.args4j.spi.OptionHandler; +import org.kohsuke.args4j.spi.Parameters; +import org.kohsuke.args4j.spi.Setter; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevTree; + +/** + * Custom argument handler {@link RevTree} from string values. + *

+ * Assumes the parser has been initialized with a Repository. + */ +public class RevTreeHandler extends OptionHandler { + private final org.eclipse.jgit.pgm.opt.CmdLineParser clp; + + /** + * Create a new handler for the command name. + *

+ * This constructor is used only by args4j. + * + * @param parser + * @param option + * @param setter + */ + public RevTreeHandler(final CmdLineParser parser, final OptionDef option, + final Setter setter) { + super(parser, option, setter); + clp = (org.eclipse.jgit.pgm.opt.CmdLineParser) parser; + } + + @Override + public int parseArguments(final Parameters params) throws CmdLineException { + final String name = params.getParameter(0); + final ObjectId id; + try { + id = clp.getRepository().resolve(name); + } catch (IOException e) { + throw new CmdLineException(e.getMessage()); + } + if (id == null) + throw new CmdLineException(name + " is not a tree"); + + final RevTree c; + try { + c = clp.getRevWalk().parseTree(id); + } catch (MissingObjectException e) { + throw new CmdLineException(name + " is not a tree"); + } catch (IncorrectObjectTypeException e) { + throw new CmdLineException(name + " is not a tree"); + } catch (IOException e) { + throw new CmdLineException("cannot read " + name + ": " + + e.getMessage()); + } + setter.addValue(c); + return 1; + } + + @Override + public String getDefaultMetaVariable() { + return "tree-ish"; + } +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java new file mode 100644 index 000000000..3378f38c1 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/opt/SubcommandHandler.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.pgm.opt; + +import java.text.MessageFormat; + +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.OptionDef; +import org.kohsuke.args4j.spi.OptionHandler; +import org.kohsuke.args4j.spi.Parameters; +import org.kohsuke.args4j.spi.Setter; +import org.eclipse.jgit.pgm.CommandCatalog; +import org.eclipse.jgit.pgm.CommandRef; +import org.eclipse.jgit.pgm.TextBuiltin; + +/** + * Custom Argument handler for jgit command selection. + *

+ * Translates a single argument string to a {@link TextBuiltin} instance which + * we can execute at runtime with the remaining arguments of the parser. + */ +public class SubcommandHandler extends OptionHandler { + /** + * Create a new handler for the command name. + *

+ * This constructor is used only by args4j. + * + * @param parser + * @param option + * @param setter + */ + public SubcommandHandler(final CmdLineParser parser, + final OptionDef option, final Setter setter) { + super(parser, option, setter); + } + + @Override + public int parseArguments(final Parameters params) throws CmdLineException { + final String name = params.getParameter(0); + final CommandRef cr = CommandCatalog.get(name); + if (cr == null) + throw new CmdLineException(MessageFormat.format( + "{0} is not a jgit command", name)); + + // Force option parsing to stop. Everything after us should + // be arguments known only to this command and must not be + // recognized by the current parser. + // + owner.stopOptionParsing(); + setter.addValue(cr.create()); + return 1; + } + + @Override + public String getDefaultMetaVariable() { + return "command"; + } +} diff --git a/org.eclipse.jgit.test/.classpath b/org.eclipse.jgit.test/.classpath new file mode 100644 index 000000000..cb944bd38 --- /dev/null +++ b/org.eclipse.jgit.test/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/org.eclipse.jgit.test/.gitignore b/org.eclipse.jgit.test/.gitignore new file mode 100644 index 000000000..9d7d1382b --- /dev/null +++ b/org.eclipse.jgit.test/.gitignore @@ -0,0 +1,3 @@ +bin +tst/todopack +trash diff --git a/org.eclipse.jgit.test/.project b/org.eclipse.jgit.test/.project new file mode 100644 index 000000000..a7242a068 --- /dev/null +++ b/org.eclipse.jgit.test/.project @@ -0,0 +1,17 @@ + + + org.eclipse.jgit.test + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/org.eclipse.jgit.test/.settings/org.eclipse.core.resources.prefs b/org.eclipse.jgit.test/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 000000000..6a9621db1 --- /dev/null +++ b/org.eclipse.jgit.test/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +#Sat Dec 20 21:21:24 CET 2008 +eclipse.preferences.version=1 +encoding//tst-rsrc/org/eclipse/jgit/patch/testGetText_BothISO88591.patch=ISO-8859-1 +encoding//tst-rsrc/org/eclipse/jgit/patch/testGetText_Convert.patch=ISO-8859-1 +encoding//tst-rsrc/org/eclipse/jgit/patch/testGetText_DiffCc.patch=ISO-8859-1 +encoding/=UTF-8 diff --git a/org.eclipse.jgit.test/.settings/org.eclipse.core.runtime.prefs b/org.eclipse.jgit.test/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 000000000..72987c252 --- /dev/null +++ b/org.eclipse.jgit.test/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,3 @@ +#Mon Mar 24 18:55:56 EDT 2008 +eclipse.preferences.version=1 +line.separator=\n diff --git a/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..8bfa5f141 --- /dev/null +++ b/org.eclipse.jgit.test/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,320 @@ +#Tue Feb 05 00:01:29 CET 2008 +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=ignore +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public +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=error +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.test/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.jgit.test/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 000000000..fce94cf0c --- /dev/null +++ b/org.eclipse.jgit.test/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,10 @@ +#Thu Dec 20 01:31:04 CET 2007 +eclipse.preferences.version=1 +formatter_profile=_JGit +formatter_settings_version=10 +internal.default.compliance=default +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= diff --git a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/SpeedTestBase.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/SpeedTestBase.java new file mode 100644 index 000000000..b53a4e430 --- /dev/null +++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/SpeedTestBase.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2008, Google Inc. + * Copyright (C) 2007-2008, Robin Rosenberg + * Copyright (C) 2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.FileReader; +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; + +/** + * Base class for performance unit test. + */ +public abstract class SpeedTestBase extends TestCase { + + /** + * The time used by native git as this is our reference. + */ + protected long nativeTime; + + /** + * Reference to the location of the Linux kernel repo. + */ + protected String kernelrepo; + + /** + * Prepare test by running a test against the Linux kernel repo first. + * + * @param refcmd + * git command to execute + * + * @throws Exception + */ + protected void prepare(String[] refcmd) throws Exception { + try { + BufferedReader bufferedReader = new BufferedReader(new FileReader("kernel.ref")); + try { + kernelrepo = bufferedReader.readLine(); + } finally { + bufferedReader.close(); + } + timeNativeGit(kernelrepo, refcmd); + nativeTime = timeNativeGit(kernelrepo, refcmd); + } catch (Exception e) { + System.out.println("Create a file named kernel.ref and put the path to the Linux kernels repository there"); + throw e; + } + } + + private static long timeNativeGit(String kernelrepo, String[] refcmd) throws IOException, + InterruptedException, Exception { + long start = System.currentTimeMillis(); + Process p = Runtime.getRuntime().exec(refcmd, null, new File(kernelrepo,"..")); + InputStream inputStream = p.getInputStream(); + InputStream errorStream = p.getErrorStream(); + byte[] buf=new byte[1024*1024]; + for (;;) + if (inputStream.read(buf) < 0) + break; + if (p.waitFor()!=0) { + int c; + while ((c=errorStream.read())!=-1) + System.err.print((char)c); + throw new Exception("git log failed"); + } + inputStream.close(); + errorStream.close(); + long stop = System.currentTimeMillis(); + return stop - start; + } +} diff --git a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/T0005_ShallowSpeedTest.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/T0005_ShallowSpeedTest.java new file mode 100644 index 000000000..97b1cf97d --- /dev/null +++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/T0005_ShallowSpeedTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2007-2008, Robin Rosenberg + * Copyright (C) 2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 junit.textui.TestRunner; + +public class T0005_ShallowSpeedTest extends SpeedTestBase { + + protected void setUp() throws Exception { + prepare(new String[] { "git", "rev-list", "365bbe0d0caaf2ba74d56556827babf0bc66965d" }); + } + + public void testShallowHistoryScan() throws IOException { + long start = System.currentTimeMillis(); + Repository db = new Repository(new File(kernelrepo)); + Commit commit = db.mapCommit("365bbe0d0caaf2ba74d56556827babf0bc66965d"); + int n = 1; + for (;;) { + ObjectId[] parents = commit.getParentIds(); + if (parents.length == 0) + break; + ObjectId parentId = parents[0]; + commit = db.mapCommit(parentId); + commit.getCommitId().name(); + ++n; + } + assertEquals(12275, n); + long stop = System.currentTimeMillis(); + long time = stop - start; + System.out.println("native="+nativeTime); + System.out.println("jgit="+time); + // ~0.750s (hot cache), ok + /* +native=1795 +jgit=722 + */ + // native git seems to run SLOWER than jgit here, at roughly half the speed + // creating the git process is not the issue here, btw. + long factor10 = (nativeTime*150/time+50)/100; + assertEquals(3, factor10); + } + + public static void main(String[] args) { + TestRunner.run(T0005_ShallowSpeedTest.class); + } +} diff --git a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/T0006_DeepSpeedTest.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/T0006_DeepSpeedTest.java new file mode 100644 index 000000000..8843ae35b --- /dev/null +++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/lib/T0006_DeepSpeedTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2007-2008, Robin Rosenberg + * Copyright (C) 2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 junit.textui.TestRunner; + +public class T0006_DeepSpeedTest extends SpeedTestBase { + + protected void setUp() throws Exception { + prepare(new String[] { "git", "rev-list", "365bbe0d0caaf2ba74d56556827babf0bc66965d","--","net/netfilter/nf_queue.c" }); + } + + public void testDeepHistoryScan() throws IOException { + long start = System.currentTimeMillis(); + Repository db = new Repository(new File(kernelrepo)); + Commit commit = db.mapCommit("365bbe0d0caaf2ba74d56556827babf0bc66965d"); + int n = 1; + for (;;) { + ObjectId[] parents = commit.getParentIds(); + if (parents.length == 0) + break; + ObjectId parentId = parents[0]; + commit = db.mapCommit(parentId); + TreeEntry m = commit.getTree().findBlobMember("net/netfilter/nf_queue.c"); + if (m != null) + commit.getCommitId().name(); + ++n; + } + + assertEquals(12275, n); + long stop = System.currentTimeMillis(); + long time = stop - start; + System.out.println("native="+nativeTime); + System.out.println("jgit="+time); + /* + native=1355 + jgit=5449 + */ + // This is not an exact factor, but we'd expect native git to perform this + // about 4 times quicker. If for some reason we find jgit to be faster than + // this the cause should be found and secured. + long factor = (time*110/nativeTime+50)/100; + assertEquals(4, factor); + } + + public static void main(String[] args) { + TestRunner.run(T0006_DeepSpeedTest.class); + } +} diff --git a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java new file mode 100644 index 000000000..60d3853d8 --- /dev/null +++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/patch/EGitPatchHistoryTest.java @@ -0,0 +1,227 @@ +/* + * 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 java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.HashSet; + +import junit.framework.TestCase; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.util.MutableInteger; +import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.TemporaryBuffer; + +public class EGitPatchHistoryTest extends TestCase { + public void testParseHistory() throws Exception { + final NumStatReader numstat = new NumStatReader(); + numstat.read(); + + final HashMap> stats = numstat.stats; + assertEquals(1211, stats.size()); + + new PatchReader(stats).read(); + } + + static class StatInfo { + int added, deleted; + } + + static class PatchReader extends CommitReader { + final HashSet offBy1; + + final HashMap> stats; + + int errors; + + PatchReader(final HashMap> s) + throws IOException { + super(new String[] { "-p" }); + stats = s; + + offBy1 = new HashSet(); + offBy1.add("9bda5ece6806cd797416eaa47c7b927cc6e9c3b2"); + } + + @Override + void onCommit(String cid, byte[] buf) { + final HashMap files = stats.remove(cid); + assertNotNull("No files for " + cid, files); + + final Patch p = new Patch(); + p.parse(buf, 0, buf.length - 1); + assertEquals("File count " + cid, files.size(), p.getFiles().size()); + if (!p.getErrors().isEmpty()) { + for (final FormatError e : p.getErrors()) { + System.out.println("error " + e.getMessage()); + System.out.println(" at " + e.getLineText()); + } + dump(buf); + fail("Unexpected error in " + cid); + } + + for (final FileHeader fh : p.getFiles()) { + final String fileName; + if (fh.getChangeType() != FileHeader.ChangeType.DELETE) + fileName = fh.getNewName(); + else + fileName = fh.getOldName(); + final StatInfo s = files.remove(fileName); + final String nid = fileName + " in " + cid; + assertNotNull("No " + nid, s); + int added = 0, deleted = 0; + for (final HunkHeader h : fh.getHunks()) { + added += h.getOldImage().getLinesAdded(); + deleted += h.getOldImage().getLinesDeleted(); + } + + if (s.added == added) { + // + } else if (s.added == added + 1 && offBy1.contains(cid)) { + // + } else { + dump(buf); + assertEquals("Added diff in " + nid, s.added, added); + } + + if (s.deleted == deleted) { + // + } else if (s.deleted == deleted + 1 && offBy1.contains(cid)) { + // + } else { + dump(buf); + assertEquals("Deleted diff in " + nid, s.deleted, deleted); + } + } + assertTrue("Missed files in " + cid, files.isEmpty()); + } + + private static void dump(final byte[] buf) { + String str; + try { + str = new String(buf, 0, buf.length - 1, "ISO-8859-1"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + System.out.println("<<" + str + ">>"); + } + } + + static class NumStatReader extends CommitReader { + final HashMap> stats = new HashMap>(); + + NumStatReader() throws IOException { + super(new String[] { "--numstat" }); + } + + @Override + void onCommit(String commitId, byte[] buf) { + final HashMap files = new HashMap(); + final MutableInteger ptr = new MutableInteger(); + while (ptr.value < buf.length) { + if (buf[ptr.value] == '\n') + break; + final StatInfo i = new StatInfo(); + i.added = RawParseUtils.parseBase10(buf, ptr.value, ptr); + i.deleted = RawParseUtils.parseBase10(buf, ptr.value + 1, ptr); + final int eol = RawParseUtils.nextLF(buf, ptr.value); + final String name = RawParseUtils.decode(Constants.CHARSET, + buf, ptr.value + 1, eol - 1); + files.put(name, i); + ptr.value = eol; + } + stats.put(commitId, files); + } + } + + static abstract class CommitReader { + private Process proc; + + CommitReader(final String[] args) throws IOException { + final String[] realArgs = new String[3 + args.length + 1]; + realArgs[0] = "git"; + realArgs[1] = "log"; + realArgs[2] = "--pretty=format:commit %H"; + System.arraycopy(args, 0, realArgs, 3, args.length); + realArgs[3 + args.length] = "a4b98ed15ea5f165a7aa0f2fd2ea6fcce6710925"; + + proc = Runtime.getRuntime().exec(realArgs); + proc.getOutputStream().close(); + proc.getErrorStream().close(); + } + + void read() throws IOException, InterruptedException { + final BufferedReader in = new BufferedReader(new InputStreamReader( + proc.getInputStream(), "ISO-8859-1")); + String commitId = null; + TemporaryBuffer buf = null; + for (;;) { + String line = in.readLine(); + if (line == null) + break; + if (line.startsWith("commit ")) { + if (buf != null) { + buf.close(); + onCommit(commitId, buf.toByteArray()); + buf.destroy(); + } + commitId = line.substring("commit ".length()); + buf = new TemporaryBuffer(); + } else if (buf != null) { + buf.write(line.getBytes("ISO-8859-1")); + buf.write('\n'); + } + } + in.close(); + assertEquals(0, proc.waitFor()); + proc = null; + } + + abstract void onCommit(String commitId, byte[] buf); + } +} diff --git a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-External-Tests (Java 6).launch b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-External-Tests (Java 6).launch new file mode 100644 index 000000000..9a9ca124d --- /dev/null +++ b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-External-Tests (Java 6).launch @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-External-Tests.launch b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-External-Tests.launch new file mode 100644 index 000000000..6735fb0a2 --- /dev/null +++ b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-External-Tests.launch @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests (Java 6).launch b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests (Java 6).launch new file mode 100644 index 000000000..a0aecf920 --- /dev/null +++ b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests (Java 6).launch @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests.launch b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests.launch new file mode 100644 index 000000000..bff41ecf0 --- /dev/null +++ b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests.launch @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/E.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/E.patch new file mode 100644 index 000000000..9b8fa98cf --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/E.patch @@ -0,0 +1,6 @@ +diff --git a/E b/E +index e69de29..7898192 100644 +--- a/E ++++ b/E +@@ -0,0 +1 @@ ++a diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/E_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/E_PostImage new file mode 100644 index 000000000..789819226 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/E_PostImage @@ -0,0 +1 @@ +a diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/E_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/E_PreImage new file mode 100644 index 000000000..e69de29bb diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/X.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/X.patch new file mode 100644 index 000000000..e5363eb20 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/X.patch @@ -0,0 +1,24 @@ +diff --git a/X b/X +index a3648a1..2d44096 100644 +--- a/X ++++ b/X +@@ -2,2 +2,3 @@ a + b ++c + d +@@ -16,4 +17,2 @@ p + q +-r +-s + t +@@ -22,4 +21,8 @@ v + w +-x +-y ++0 ++1 ++2 ++3 ++4 ++5 + z diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/X_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/X_PostImage new file mode 100644 index 000000000..2d4409602 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/X_PostImage @@ -0,0 +1,28 @@ +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +t +u +v +w +0 +1 +2 +3 +4 +5 +z diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/X_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/X_PreImage new file mode 100644 index 000000000..a3648a1eb --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/X_PreImage @@ -0,0 +1,25 @@ +a +b +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Y.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Y.patch new file mode 100644 index 000000000..a2c9a0bb4 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Y.patch @@ -0,0 +1,8 @@ +diff --git a/Y b/Y +index 2e65efe..7898192 100644 +--- a/Y ++++ b/Y +@@ -1 +1 @@ +-a +\ No newline at end of file ++a diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Y_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Y_PostImage new file mode 100644 index 000000000..789819226 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Y_PostImage @@ -0,0 +1 @@ +a diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Y_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Y_PreImage new file mode 100644 index 000000000..2e65efe2a --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Y_PreImage @@ -0,0 +1 @@ +a \ No newline at end of file diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Z.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Z.patch new file mode 100644 index 000000000..35a06d647 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Z.patch @@ -0,0 +1,8 @@ +diff --git a/Z b/Z +index 7898192..2e65efe 100644 +--- a/Z ++++ b/Z +@@ -1 +1 @@ +-a ++a +\ No newline at end of file diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Z_PostImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Z_PostImage new file mode 100644 index 000000000..2e65efe2a --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Z_PostImage @@ -0,0 +1 @@ +a \ No newline at end of file diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Z_PreImage b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Z_PreImage new file mode 100644 index 000000000..789819226 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/Z_PreImage @@ -0,0 +1 @@ +a diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext0.out b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext0.out new file mode 100644 index 000000000..d36e3fa0b --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext0.out @@ -0,0 +1,18 @@ +diff --git a/X b/X +index a3648a1..2d44096 100644 +--- a/X ++++ b/X +@@ -2,0 +3 @@ ++c +@@ -17,2 +17,0 @@ +-r +-s +@@ -23,2 +22,6 @@ +-x +-y ++0 ++1 ++2 ++3 ++4 ++5 diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext1.out b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext1.out new file mode 100644 index 000000000..d0d847d60 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext1.out @@ -0,0 +1,24 @@ +diff --git a/X b/X +index a3648a1..2d44096 100644 +--- a/X ++++ b/X +@@ -2,2 +2,3 @@ + b ++c + d +@@ -16,4 +17,2 @@ + q +-r +-s + t +@@ -22,4 +21,8 @@ + w +-x +-y ++0 ++1 ++2 ++3 ++4 ++5 + z diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext10.out b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext10.out new file mode 100644 index 000000000..1d4f24277 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext10.out @@ -0,0 +1,37 @@ +diff --git a/X b/X +index a3648a1..2d44096 100644 +--- a/X ++++ b/X +@@ -1,25 +1,28 @@ + a + b ++c + d + e + f + g + h + i + j + k + l + m + n + o + p + q +-r +-s + t + u + v + w +-x +-y ++0 ++1 ++2 ++3 ++4 ++5 + z diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext100.out b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext100.out new file mode 100644 index 000000000..1d4f24277 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext100.out @@ -0,0 +1,37 @@ +diff --git a/X b/X +index a3648a1..2d44096 100644 +--- a/X ++++ b/X +@@ -1,25 +1,28 @@ + a + b ++c + d + e + f + g + h + i + j + k + l + m + n + o + p + q +-r +-s + t + u + v + w +-x +-y ++0 ++1 ++2 ++3 ++4 ++5 + z diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext3.out b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext3.out new file mode 100644 index 000000000..256401664 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext3.out @@ -0,0 +1,30 @@ +diff --git a/X b/X +index a3648a1..2d44096 100644 +--- a/X ++++ b/X +@@ -1,5 +1,6 @@ + a + b ++c + d + e + f +@@ -14,12 +15,14 @@ + o + p + q +-r +-s + t + u + v + w +-x +-y ++0 ++1 ++2 ++3 ++4 ++5 + z diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext5.out b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext5.out new file mode 100644 index 000000000..3073c5f09 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/diff/testContext5.out @@ -0,0 +1,34 @@ +diff --git a/X b/X +index a3648a1..2d44096 100644 +--- a/X ++++ b/X +@@ -1,7 +1,8 @@ + a + b ++c + d + e + f + g + h +@@ -12,14 +13,16 @@ + m + n + o + p + q +-r +-s + t + u + v + w +-x +-y ++0 ++1 ++2 ++3 ++4 ++5 + z diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/.gitattributes b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/.gitattributes new file mode 100644 index 000000000..b38f87f9e --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/.gitattributes @@ -0,0 +1 @@ +*.patch -crlf diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testEditList_Types.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testEditList_Types.patch new file mode 100644 index 000000000..e5363eb20 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testEditList_Types.patch @@ -0,0 +1,24 @@ +diff --git a/X b/X +index a3648a1..2d44096 100644 +--- a/X ++++ b/X +@@ -2,2 +2,3 @@ a + b ++c + d +@@ -16,4 +17,2 @@ p + q +-r +-s + t +@@ -22,4 +21,8 @@ v + w +-x +-y ++0 ++1 ++2 ++3 ++4 ++5 + z diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_BodyTooLong.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_BodyTooLong.patch new file mode 100644 index 000000000..9506198b1 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_BodyTooLong.patch @@ -0,0 +1,17 @@ +diff --git a/org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java b/org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java +index da7e704..34ce04a 100644 +--- a/org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java ++++ b/org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java +@@ -109,4 +109,11 @@ assertTrue(Arrays.equals(values.toArray(), repositoryConfig + .getStringList("my", null, "somename"))); + checkFile(cfgFile, "[my]\n\tsomename = value1\n\tsomename = value2\n"); + } ++ ++ public void test006_readCaseInsensitive() throws IOException { ++ final File path = writeTrashFile("config_001", "[Foo]\nBar\n"); ++ RepositoryConfig repositoryConfig = new RepositoryConfig(null, path); ++BAD LINE ++ assertEquals(true, repositoryConfig.getBoolean("foo", null, "bar", false)); ++ assertEquals("", repositoryConfig.getString("foo", null, "bar")); ++ } + } diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_CcTruncatedOld.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_CcTruncatedOld.patch new file mode 100644 index 000000000..1bbcfb54d --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_CcTruncatedOld.patch @@ -0,0 +1,24 @@ +commit 1a56639bbea8e8cbfbe5da87746de97f9217ce9b +Date: Tue May 13 00:43:56 2008 +0200 + ... + +diff --cc org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java +index 169356b,dd8c317..fd85931 +mode 100644,100644..100755 +--- a/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java ++++ b/org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java +@@@ -55,12 -163,13 +163,15 @@@ public class UIText extends NLS + + /** */ + public static String ResourceHistory_toggleCommentWrap; ++ + /** */ + + /** */ + public static String ResourceHistory_toggleRevDetail; + /** */ + public static String ResourceHistory_toggleRevComment; + /** */ + public static String ResourceHistory_toggleTooltips; + + +commit 1a56639bbea8e8cbfbe5da87746de97f9217ce9b diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_DisconnectedHunk.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_DisconnectedHunk.patch new file mode 100644 index 000000000..323597041 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_DisconnectedHunk.patch @@ -0,0 +1,30 @@ +From: A. U. Thor + +@@ -109,4 +109,11 @@ assertTrue(Arrays.equals(values.toArray(), repositoryConfig + .getStringList("my", null, "somename"))); + checkFile(cfgFile, "[my]\n\tsomename = value1\n\tsomename = value2\n"); + } ++ ++ public void test006_readCaseInsensitive() throws IOException { ++ final File path = writeTrashFile("config_001", "[Foo]\nBar\n"); ++ RepositoryConfig repositoryConfig = new RepositoryConfig(null, path); ++ assertEquals(true, repositoryConfig.getBoolean("foo", null, "bar", false)); ++ assertEquals("", repositoryConfig.getString("foo", null, "bar")); ++ } + } +diff --git a/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java b/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java +index 45c2f8a..3291bba 100644 +--- a/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java ++++ b/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java +@@ -236,9 +236,9 @@ protected boolean getBoolean(final String section, String subsection, + return defaultValue; + + n = n.toLowerCase(); +- if (MAGIC_EMPTY_VALUE.equals(n) || "yes".equals(n) || "true".equals(n) || "1".equals(n)) { ++ if (MAGIC_EMPTY_VALUE.equals(n) || "yes".equalsIgnoreCase(n) || "true".equalsIgnoreCase(n) || "1".equals(n)) { + return true; +- } else if ("no".equals(n) || "false".equals(n) || "0".equals(n)) { ++ } else if ("no".equalsIgnoreCase(n) || "false".equalsIgnoreCase(n) || "0".equalsIgnoreCase(n)) { + return false; + } else { + throw new IllegalArgumentException("Invalid boolean value: " diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_GarbageBetweenFiles.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_GarbageBetweenFiles.patch new file mode 100644 index 000000000..9f6fd6b8f --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_GarbageBetweenFiles.patch @@ -0,0 +1,33 @@ +diff --git a/org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java b/org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java +index da7e704..34ce04a 100644 +--- a/org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java ++++ b/org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java +@@ -109,4 +109,11 @@ assertTrue(Arrays.equals(values.toArray(), repositoryConfig + .getStringList("my", null, "somename"))); + checkFile(cfgFile, "[my]\n\tsomename = value1\n\tsomename = value2\n"); + } ++ ++ public void test006_readCaseInsensitive() throws IOException { ++ final File path = writeTrashFile("config_001", "[Foo]\nBar\n"); ++ RepositoryConfig repositoryConfig = new RepositoryConfig(null, path); ++ assertEquals(true, repositoryConfig.getBoolean("foo", null, "bar", false)); ++ assertEquals("", repositoryConfig.getString("foo", null, "bar")); ++ } + } +I AM NOT HERE +diff --git a/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java b/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java +index 45c2f8a..3291bba 100644 +--- a/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java ++++ b/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java +@@ -236,9 +236,9 @@ protected boolean getBoolean(final String section, String subsection, + return defaultValue; + + n = n.toLowerCase(); +- if (MAGIC_EMPTY_VALUE.equals(n) || "yes".equals(n) || "true".equals(n) || "1".equals(n)) { ++ if (MAGIC_EMPTY_VALUE.equals(n) || "yes".equalsIgnoreCase(n) || "true".equalsIgnoreCase(n) || "1".equals(n)) { + return true; +- } else if ("no".equals(n) || "false".equals(n) || "0".equals(n)) { ++ } else if ("no".equalsIgnoreCase(n) || "false".equalsIgnoreCase(n) || "0".equalsIgnoreCase(n)) { + return false; + } else { + throw new IllegalArgumentException("Invalid boolean value: " diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_GitBinaryNoForwardHunk.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_GitBinaryNoForwardHunk.patch new file mode 100644 index 000000000..e3f33075a --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_GitBinaryNoForwardHunk.patch @@ -0,0 +1,10 @@ + create mode 100644 org.spearce.egit.ui/icons/toolbar/pushe.png + +diff --git a/org.spearce.egit.ui/icons/toolbar/fetchd.png b/org.spearce.egit.ui/icons/toolbar/fetchd.png +new file mode 100644 +index 0000000000000000000000000000000000000000..4433c543f2a52b586a3ed5e31b138244107bc239 +GIT binary patch + +diff --git a/org.spearce.egit.ui/icons/toolbar/fetche.png b/org.spearce.egit.ui/icons/toolbar/fetche.png +new file mode 100644 +index 0000000000000000000000000000000000000000..0ffeb419e6ab302caa5e58661854b33853dc43dc diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_TruncatedNew.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_TruncatedNew.patch new file mode 100644 index 000000000..f10a433be --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_TruncatedNew.patch @@ -0,0 +1,15 @@ +diff --git a/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java b/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java +index 45c2f8a..3291bba 100644 +--- a/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java ++++ b/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java +@@ -236,9 +236,9 @@ protected boolean getBoolean(final String section, String subsection, + return defaultValue; + + n = n.toLowerCase(); +- if (MAGIC_EMPTY_VALUE.equals(n) || "yes".equals(n) || "true".equals(n) || "1".equals(n)) { + return true; +- } else if ("no".equals(n) || "false".equals(n) || "0".equals(n)) { ++ } else if ("no".equalsIgnoreCase(n) || "false".equalsIgnoreCase(n) || "0".equalsIgnoreCase(n)) { + return false; + } else { + throw new IllegalArgumentException("Invalid boolean value: " diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_TruncatedOld.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_TruncatedOld.patch new file mode 100644 index 000000000..42363c61c --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testError_TruncatedOld.patch @@ -0,0 +1,15 @@ +diff --git a/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java b/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java +index 45c2f8a..3291bba 100644 +--- a/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java ++++ b/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java +@@ -236,9 +236,9 @@ protected boolean getBoolean(final String section, String subsection, + return defaultValue; + + n = n.toLowerCase(); +- if (MAGIC_EMPTY_VALUE.equals(n) || "yes".equals(n) || "true".equals(n) || "1".equals(n)) { ++ if (MAGIC_EMPTY_VALUE.equals(n) || "yes".equalsIgnoreCase(n) || "true".equalsIgnoreCase(n) || "1".equals(n)) { + return true; ++ } else if ("no".equalsIgnoreCase(n) || "false".equalsIgnoreCase(n) || "0".equalsIgnoreCase(n)) { + return false; + } else { + throw new IllegalArgumentException("Invalid boolean value: " diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testGetText_BothISO88591.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testGetText_BothISO88591.patch new file mode 100644 index 000000000..8224fcc21 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testGetText_BothISO88591.patch @@ -0,0 +1,21 @@ +diff --git a/X b/X +index 014ef30..8c80a36 100644 +--- a/X ++++ b/X +@@ -1,7 +1,7 @@ + a + b + c +-Ångström ++line 4 Ångström + d + e + f +@@ -13,6 +13,6 @@ k + l + m + n +-Ångström ++Ångström; line 16 + o + p diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testGetText_Convert.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testGetText_Convert.patch new file mode 100644 index 000000000..a43fef584 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testGetText_Convert.patch @@ -0,0 +1,21 @@ +diff --git a/X b/X +index 014ef30..209db0d 100644 +--- a/X ++++ b/X +@@ -1,7 +1,7 @@ + a + b + c +-Ångström ++Ã…ngström + d + e + f +@@ -13,6 +13,6 @@ k + l + m + n +-Ångström ++Ã…ngström + o + p diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testGetText_DiffCc.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testGetText_DiffCc.patch new file mode 100644 index 000000000..3f74a5223 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testGetText_DiffCc.patch @@ -0,0 +1,13 @@ +diff --cc X +index bdfc9f4,209db0d..474bd69 +--- a/X ++++ b/X +@@@ -1,7 -1,7 +1,7 @@@ + a +--b + c + +test Ångström ++ Ã…ngström + d + e + f diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testGetText_NoBinary.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testGetText_NoBinary.patch new file mode 100644 index 000000000..e4968dc4e --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testGetText_NoBinary.patch @@ -0,0 +1,4 @@ +diff --git a/org.spearce.egit.ui/icons/toolbar/fetchd.png b/org.spearce.egit.ui/icons/toolbar/fetchd.png +new file mode 100644 +index 0000000..4433c54 +Binary files /dev/null and b/org.spearce.egit.ui/icons/toolbar/fetchd.png differ diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_AddNoNewline.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_AddNoNewline.patch new file mode 100644 index 000000000..3060952e3 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_AddNoNewline.patch @@ -0,0 +1,20 @@ +From ca4719a4b2d93a469f61d1ddfb3e39ecbabfcd69 Mon Sep 17 00:00:00 2001 +From: Shawn O. Pearce +Date: Fri, 12 Dec 2008 12:35:14 -0800 +Subject: [PATCH] introduce no lf again + +--- + a | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/a b/a +index f2ad6c7..c59d9b6 100644 +--- a/a ++++ b/a +@@ -1 +1 @@ +-c ++d +\ No newline at end of file +-- +1.6.1.rc2.306.ge5d5e + diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_CcDeleteFile.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_CcDeleteFile.patch new file mode 100644 index 000000000..2654e0979 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_CcDeleteFile.patch @@ -0,0 +1,12 @@ +commit 740709ece2412856c0c3eabd4dc4a4cf115b0de6 +Merge: 5c19b43... 13a2c0d... +Author: Shawn O. Pearce +Date: Fri Dec 12 13:26:52 2008 -0800 + + Merge branch 'b' into d + +diff --cc a +index 7898192,2e65efe..0000000 +deleted file mode 100644,100644 +--- a/a ++++ /dev/null diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_CcNewFile.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_CcNewFile.patch new file mode 100644 index 000000000..1a9b7b0ee --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_CcNewFile.patch @@ -0,0 +1,14 @@ +commit 6cb8160a4717d51fd3cc0baf721946daa60cf921 +Merge: 5c19b43... 13a2c0d... +Author: Shawn O. Pearce +Date: Fri Dec 12 13:26:52 2008 -0800 + + Merge branch 'b' into d + +diff --cc d +index 0000000,0000000..4bcfe98 +new file mode 100644 +--- /dev/null ++++ b/d +@@@ -1,0 -1,0 +1,1 @@@ +++d diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_ConfigCaseInsensitive.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_ConfigCaseInsensitive.patch new file mode 100644 index 000000000..bfb9b15dc --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_ConfigCaseInsensitive.patch @@ -0,0 +1,67 @@ +From ce9b593ddf2530613f6da9d7f7e4a5ff93da8b36 Mon Sep 17 00:00:00 2001 +From: Robin Rosenberg +Date: Mon, 13 Oct 2008 00:50:59 +0200 +Subject: [PATCH] git config file is case insensitive + +Keys are now always compared with ignore case rules. + +Signed-off-by: Robin Rosenberg +Signed-off-by: Shawn O. Pearce +--- + .../org/spearce/jgit/lib/RepositoryConfigTest.java | 7 +++++++ + .../src/org/spearce/jgit/lib/RepositoryConfig.java | 8 ++++---- + 2 files changed, 11 insertions(+), 4 deletions(-) + +diff --git a/org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java b/org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java +index da7e704..34ce04a 100644 +--- a/org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java ++++ b/org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java +@@ -109,4 +109,11 @@ assertTrue(Arrays.equals(values.toArray(), repositoryConfig + .getStringList("my", null, "somename"))); + checkFile(cfgFile, "[my]\n\tsomename = value1\n\tsomename = value2\n"); + } ++ ++ public void test006_readCaseInsensitive() throws IOException { ++ final File path = writeTrashFile("config_001", "[Foo]\nBar\n"); ++ RepositoryConfig repositoryConfig = new RepositoryConfig(null, path); ++ assertEquals(true, repositoryConfig.getBoolean("foo", null, "bar", false)); ++ assertEquals("", repositoryConfig.getString("foo", null, "bar")); ++ } + } +diff --git a/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java b/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java +index 45c2f8a..3291bba 100644 +--- a/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java ++++ b/org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java +@@ -236,9 +236,9 @@ protected boolean getBoolean(final String section, String subsection, + return defaultValue; + + n = n.toLowerCase(); +- if (MAGIC_EMPTY_VALUE.equals(n) || "yes".equals(n) || "true".equals(n) || "1".equals(n)) { ++ if (MAGIC_EMPTY_VALUE.equals(n) || "yes".equalsIgnoreCase(n) || "true".equalsIgnoreCase(n) || "1".equals(n)) { + return true; +- } else if ("no".equals(n) || "false".equals(n) || "0".equals(n)) { ++ } else if ("no".equalsIgnoreCase(n) || "false".equalsIgnoreCase(n) || "0".equalsIgnoreCase(n)) { + return false; + } else { + throw new IllegalArgumentException("Invalid boolean value: " +@@ -300,7 +300,7 @@ public String getString(final String section, String subsection, final String na + final Set result = new HashSet(); + + for (final Entry e : entries) { +- if (section.equals(e.base) && e.extendedBase != null) ++ if (section.equalsIgnoreCase(e.base) && e.extendedBase != null) + result.add(e.extendedBase); + } + if (baseConfig != null) +@@ -954,7 +954,7 @@ private static boolean eq(final String a, final String b) { + return true; + if (a == null || b == null) + return false; +- return a.equals(b); ++ return a.equalsIgnoreCase(b); + } + } + } +-- +1.6.1.rc2.299.gead4c + diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_FixNoNewline.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_FixNoNewline.patch new file mode 100644 index 000000000..e8af2e719 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_FixNoNewline.patch @@ -0,0 +1,20 @@ +From 1beb3ec1fe68ff18b0287396096442e12c34787a Mon Sep 17 00:00:00 2001 +From: Shawn O. Pearce +Date: Fri, 12 Dec 2008 12:29:45 -0800 +Subject: [PATCH] make c and add lf + +--- + a | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/a b/a +index 2e65efe..f2ad6c7 100644 +--- a/a ++++ b/a +@@ -1 +1 @@ +-a +\ No newline at end of file ++c +-- +1.6.1.rc2.306.ge5d5e + diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_GitBinaryDelta.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_GitBinaryDelta.patch new file mode 100644 index 000000000..5b2c9c626 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_GitBinaryDelta.patch @@ -0,0 +1,21 @@ +From 7e49721ad0efdec3a81e20bc58e385ea5d2b87b7 Mon Sep 17 00:00:00 2001 +From: Shawn O. Pearce +Date: Fri, 12 Dec 2008 12:45:17 -0800 +Subject: [PATCH] make zero have a 3 + +--- + zero.bin | Bin 4096 -> 4096 bytes + 1 files changed, 0 insertions(+), 0 deletions(-) + +diff --git a/zero.bin b/zero.bin +index 08e7df176454f3ee5eeda13efa0adaa54828dfd8..d70d8710b6d32ff844af0ee7c247e4b4b051867f 100644 +GIT binary patch +delta 12 +TcmZorXi%6C%4ociaTPxR8IA+R + +delta 11 +ScmZorXi(Uguz-JJK>`37u>@iO + +-- +1.6.1.rc2.306.ge5d5e + diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_GitBinaryLiteral.patch b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_GitBinaryLiteral.patch new file mode 100644 index 000000000..ab7b23591 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/patch/testParse_GitBinaryLiteral.patch @@ -0,0 +1,135 @@ +From 8363f12135a7d0ff0b5fea7d5a35d294c0479518 Mon Sep 17 00:00:00 2001 +From: Robin Rosenberg +Date: Tue, 23 Sep 2008 22:19:19 +0200 +Subject: [PATCH] Push and fetch icons for the toolbar + +Signed-off-by: Robin Rosenberg +Signed-off-by: Shawn O. Pearce +--- + org.spearce.egit.ui/icons/toolbar/fetchd.png | Bin 0 -> 359 bytes + org.spearce.egit.ui/icons/toolbar/fetche.png | Bin 0 -> 393 bytes + org.spearce.egit.ui/icons/toolbar/pushd.png | Bin 0 -> 372 bytes + org.spearce.egit.ui/icons/toolbar/pushe.png | Bin 0 -> 404 bytes + org.spearce.egit.ui/plugin.xml | 32 ++++++++++++++----------- + 5 files changed, 18 insertions(+), 14 deletions(-) + create mode 100644 org.spearce.egit.ui/icons/toolbar/fetchd.png + create mode 100644 org.spearce.egit.ui/icons/toolbar/fetche.png + create mode 100644 org.spearce.egit.ui/icons/toolbar/pushd.png + create mode 100644 org.spearce.egit.ui/icons/toolbar/pushe.png + +diff --git a/org.spearce.egit.ui/icons/toolbar/fetchd.png b/org.spearce.egit.ui/icons/toolbar/fetchd.png +new file mode 100644 +index 0000000000000000000000000000000000000000..4433c543f2a52b586a3ed5e31b138244107bc239 +GIT binary patch +literal 359 +zcmV-t0hs=YP)VW2}+n! +zO1rKr0DRxqm&=7b&q>piUayDaIKlvw2}+e_&-3(PFkmzq(Q380-|r+zg6Da9KA(#Q +zPa2QMl^Gz*GK3HmMPby1VQ9_(U^bf>W`H=3+3)uhMZxuY<#al++wJIfyXFi47K?>p +z2GCj~rNp*vI-L&fb{osGh@!|$@ci;S2_arv_(sil1xb<+1Odb0kjLY}`F!ShJaW6; +z>Lr*?r);;|7ihoV2SC*MjhoFzuh;A9KAB9aMXCk(Pk$Loi}X0uxcmSB002ovPDHLk +FV1lxPoI3yj + +literal 0 +HcmV?d00001 + +diff --git a/org.spearce.egit.ui/icons/toolbar/fetche.png b/org.spearce.egit.ui/icons/toolbar/fetche.png +new file mode 100644 +index 0000000000000000000000000000000000000000..0ffeb419e6ab302caa5e58661854b33853dc43dc +GIT binary patch +literal 393 +zcmV;40e1e0P)*L3{^aqJcvRqCsRQ6~SndH7ExK +zk=KcyE?#y6(aupYt$(ujUi|ChXYD1Vl>A3Z=W-tL|B2KE=%pm#uoxNA1!yxqxEMW& +zB>{jQO^yT+ogtn_{8Ep$Aq3h-C?o|y>g-6?-!e46K4}{7I2X6^?w$w$wKqXWo#uE< +zlN$@u$mIiCW0N$hIYc2#Jf_L5pe_`875HfeP>nhW1zLv1R!iSvNdTZ7`q(*62#YbV +zQhB;#V#z_Hl;tD;jPm%3!!_Fv=xqj&EpW_lqPo^m>_wFE9KxQ3t1@8v1#@h(gk?2k +zU%h_@BTD_vVB{6b=^Lij^3Dkk>j +n^=YB|UiI3T3toz$0fY1nZ1068v8@+b00000NkvXXu0mjfWwNMg + +literal 0 +HcmV?d00001 + +diff --git a/org.spearce.egit.ui/icons/toolbar/pushd.png b/org.spearce.egit.ui/icons/toolbar/pushd.png +new file mode 100644 +index 0000000000000000000000000000000000000000..22c3f7bf17b8b5a931c7527d768af13607b03bce +GIT binary patch +literal 372 +zcmV-)0gL{LP)A(?&%>K?5S92zkHxGyeyN +zQSnxRymxZ%OQJ-CZt%;)O$w?l-T1yl~1VO;{ +zdS$=gv)ODopU<8HfZ1#YAcMg`B@Q~B4vWRYJJDL}%|UCO8Yd6XZnu?)$aFeQidwCf +z_mE--u|}hjN&o=H7-fukIg4hqnKXNVchu|kh_lCf`xbzwX88RJ-{>O;Y5D>6^@Sy# +SDlMe|0000w|sG%6%VHSk(UL*L41dvA&3Z?G>L*BB*n0rWDTl8 +zL8Pb?Jzc!)CSJ}#$rF8}#a?Uu`(JCbg_M&2pmu`H$+oP&=V*R^(bPY1%&ibu+ZV$G +zgp`ttNBF4=c=f%6i@vsq5!CR9fSfc-IT0lZ>^0`E2vbS?r{1v +z8l^m+g%^~^H#FDePyq!%wm_SSqPmu`yJKk6QAXzdroz+R(7L#RCJHY0YK_74TR+C&ZX!&h^>3c +zJvdA^W^@l;f6eS*z&I*^D|{frVpE>&7273F76LY=;y1$BWF(Q0qALI}5jqkZAq&fh +y^_oorR)}l`>CE22@+$y+&Cvb}|KU##2Jr)k?t0Dap2#Es0000 + + ++ class="org.spearce.egit.ui.internal.actions.FetchAction" ++ disabledIcon="icons/toolbar/fetchd.png" ++ icon="icons/toolbar/fetche.png" ++ id="org.spearce.egit.ui.actionfetch" ++ label="%FetchAction_label" ++ menubarPath="org.spearce.egit.ui.gitmenu/repo" ++ style="push" ++ toolbarPath="org.spearce.egit.ui" ++ tooltip="%FetchAction_tooltip"> + + ++ class="org.spearce.egit.ui.internal.actions.PushAction" ++ disabledIcon="icons/toolbar/pushd.png" ++ icon="icons/toolbar/pushe.png" ++ id="org.spearce.egit.ui.actionpush" ++ label="%PushAction_label" ++ menubarPath="org.spearce.egit.ui.gitmenu/repo" ++ style="push" ++ toolbarPath="org.spearce.egit.ui" ++ tooltip="%PushAction_tooltip"> + + +Date: Tue, 23 Sep 2008 22:19:19 +0200 +Subject: [PATCH] Push and fetch icons for the toolbar + +Signed-off-by: Robin Rosenberg +Signed-off-by: Shawn O. Pearce +--- + org.spearce.egit.ui/icons/toolbar/fetchd.png | Bin 0 -> 359 bytes + org.spearce.egit.ui/icons/toolbar/fetche.png | Bin 0 -> 393 bytes + org.spearce.egit.ui/icons/toolbar/pushd.png | Bin 0 -> 372 bytes + org.spearce.egit.ui/icons/toolbar/pushe.png | Bin 0 -> 404 bytes + org.spearce.egit.ui/plugin.xml | 32 ++++++++++++++----------- + 5 files changed, 18 insertions(+), 14 deletions(-) + create mode 100644 org.spearce.egit.ui/icons/toolbar/fetchd.png + create mode 100644 org.spearce.egit.ui/icons/toolbar/fetche.png + create mode 100644 org.spearce.egit.ui/icons/toolbar/pushd.png + create mode 100644 org.spearce.egit.ui/icons/toolbar/pushe.png + +diff --git a/org.spearce.egit.ui/icons/toolbar/fetchd.png b/org.spearce.egit.ui/icons/toolbar/fetchd.png +new file mode 100644 +index 0000000..4433c54 +Binary files /dev/null and b/org.spearce.egit.ui/icons/toolbar/fetchd.png differ +diff --git a/org.spearce.egit.ui/icons/toolbar/fetche.png b/org.spearce.egit.ui/icons/toolbar/fetche.png +new file mode 100644 +index 0000000..0ffeb41 +Binary files /dev/null and b/org.spearce.egit.ui/icons/toolbar/fetche.png differ +diff --git a/org.spearce.egit.ui/icons/toolbar/pushd.png b/org.spearce.egit.ui/icons/toolbar/pushd.png +new file mode 100644 +index 0000000..22c3f7b +Binary files /dev/null and b/org.spearce.egit.ui/icons/toolbar/pushd.png differ +diff --git a/org.spearce.egit.ui/icons/toolbar/pushe.png b/org.spearce.egit.ui/icons/toolbar/pushe.png +new file mode 100644 +index 0000000..1f99f0a +Binary files /dev/null and b/org.spearce.egit.ui/icons/toolbar/pushe.png differ +diff --git a/org.spearce.egit.ui/plugin.xml b/org.spearce.egit.ui/plugin.xml +index 7c98688..ee8a5a0 100644 +--- a/org.spearce.egit.ui/plugin.xml ++++ b/org.spearce.egit.ui/plugin.xml +@@ -272,22 +272,26 @@ + + + ++ class="org.spearce.egit.ui.internal.actions.FetchAction" ++ disabledIcon="icons/toolbar/fetchd.png" ++ icon="icons/toolbar/fetche.png" ++ id="org.spearce.egit.ui.actionfetch" ++ label="%FetchAction_label" ++ menubarPath="org.spearce.egit.ui.gitmenu/repo" ++ style="push" ++ toolbarPath="org.spearce.egit.ui" ++ tooltip="%FetchAction_tooltip"> + + ++ class="org.spearce.egit.ui.internal.actions.PushAction" ++ disabledIcon="icons/toolbar/pushd.png" ++ icon="icons/toolbar/pushe.png" ++ id="org.spearce.egit.ui.actionpush" ++ label="%PushAction_label" ++ menubarPath="org.spearce.egit.ui.gitmenu/repo" ++ style="push" ++ toolbarPath="org.spearce.egit.ui" ++ tooltip="%PushAction_tooltip"> + + >master.txt +git add master.txt +git_commit -a -m "On master" + +echo "On master" >>master.txt +git_commit -a -m "On master again" + +git checkout -b a 6c8b137b1c652731597c89668f417b8695f28dd7 +mkdir a + +echo a1 >>a/a1.txt +git add a/a1.txt +git_commit -a -m "First a/a1" + +echo a2 >>a/a2.txt +git add a/a2.txt +git_commit -a -m "First a/a2" + +git merge master + +echo a1 >>a/a1.txt +git add a/a1.txt +git_commit -a -m "Second a/a1" +git branch pa + +echo a2 >>a/a2.txt +git add a/a2.txt +git_commit -a -m "Second a/a2" + +git checkout -b b 58be4659bb571194ed4562d04b359d26216f526e + +mkdir b +echo b1 >>b/b1.txt +git add b/b1.txt +git_commit -a -m "First b/b1" + +echo b2 >>b/b2.txt +git add b/b2.txt +git_commit -a -m "First b/b2" + +git merge a + +echo b1 >>b/b1.txt +git add b/b1.txt +git_commit -a -m "Second b/b1" + +echo b2 >>b/b2.txt +git add b/b2.txt +git_commit -a -m "Second b/b2" + +rm -rf a b c master.txt +mkdir c +rm -f ./git/index +echo ref: refs/heads/c >.git/HEAD + +echo c1 >>c/c1.txt +git add c/c1.txt +git_commit -a -m "First c/c1, no parent" + +echo c2 >>c/c2.txt +git add c/c2.txt +git_commit -a -m "First c/c2" + +git_merge a + +echo c1 >>c/c1.txt +git add c/c2.txt +git_commit -a -m "Second c/c1" + +echo c2 >>c/c2.txt +git add c/c2.txt +git_commit -a -m "Second c/c2" + +git_merge b + +git checkout -b d a + +echo "a1" >>a/a1 +git add a/a1 +git_commit -a -m "Third a/a1" + +git checkout -b e a + +echo "a1" >>a/a1 +git add a/a1 +git_commit -a -m "Fourth a/a1" + +git checkout master + +git_merge c d e + +git repack -d + +git tag A a +git tag -a -m "An annotated tag" B a^ + +git repack -d + +Bnth=B +for nth in 2nd 3rd 4th 5th 6th 7th 8th 9th 10th; do + git tag -a -m "An $nth level annotated tag" "B$nth" "$Bnth" + Bnth="B$nth" +done + +git repack -d + +git checkout -b f a +mkdir f +echo "an eff" >f/f +git add f/f +git commit -m "An eff" +git checkout -b g a +mkdir f +echo "an F" >f/f +git add f/f +git commit -m "An F" + +git repack -d + +git checkout -b symlink master +ln -s c/c1.txt symlink.txt +git add symlink.txt +git_commit -m "A symlink" + +git checkout -b gitlink master +git submodule add "$(pwd)/.git" submodule +git_commit -m "A gitlink" + +git repack -d +git pack-refs --all + +gitk --all master diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.index b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.index new file mode 100644 index 0000000000000000000000000000000000000000..215da649e1c68079fb03f4f9bc0f196cca9855c8 GIT binary patch literal 134799 zcma%^30zFw8^`ao@B5y1L^Ul+La2z6LQ)YjO;e3lv$W8n2vL!gQW6PClwD+(l4J=L z6463MBt`PxnYp)_(>r&{=l}UUulN7|e}B(;&U5y2&n$3soex0}9|Q>=a!9Lpg#Q^r zEd>3Aa9LIFASnJAgj@a{g5-C?|FwBw?#8t`FC0QviOjnstmtqZqo!t<9dTjl7o2&-J#5FBeD?y_%7P32?UzRoM{!8KC5*7P`?J9i$MuppF34L=R4lac)GEvv6(R~63z_PAC!+;L?;E*5fvyUe3Jg= zwN73~{k-A_n*%OMDXm6h=u8`jPAaG)G_`h_bmq-hbEhOml&<+ZY#tM5n0Rdr9rJPM z>;ZLfkB7e&zN_!~)R#W=Lsautvuv>K(%WO`Sd2qwFQ~&u=;@k6eWE6HnDEXouw<9R zu|B=|yT;I&J`SBUPzU$%mbPMv_iNP^C2Gk(OS@lvjTZQGdJG-Qap>$DrSrjG?73m0 ze{%W`(F@TtUOqg1ZQYHjW9ZBnhfX@EhF^%%7;h)QZ^B6kgX*UN!9rKQ+Zc9qo;lTZi z_EeVGc^)Wkoo{`RXFemnG@8e#Me;cZV4V&3D0s& z8&^K(f;u9T`e)ADwZG4;@7KjgUKggsU-C05`8S5nc=GuWsKY;@

wmy64a1;2V{ z3U7!?z6ebmOJ_X!oHt5GmtNOL?|1X%dt)#gR^WW9W=0&k8^t{w>esw~?1?%(*Swpq}p53w2vKXG|PJXFPd!4Ac?0 zQtC9zzR)Lkz_mK;bhg)@EfpOLpN*k2o;)iAbtFyWdS5(w3Jno;Mfz$kZA|LA>DhT~ z44v`h*>O-uFiYs5q0wOH_gyA=a2-YH73`kWM;}9HJpJYgP)Eqrge;^~H7zcA`=Y3< z>8UMmm3tP(jiEE1`Nm05hhJK1-p^eNe>^hGip!m*^l_V0v7U0_7&=ztnAa46I+l;0 zSna<$+5Y=m$>B{VCm5=J0|3K}+gs!u?ZaN5;?@Py0L#>hOOm9rFKU z&^#g5EX6eMbp5&&|Krw>4}XLuWkg z^9-mXa*Hp)LOv^IwTz{6I#Iv%NZtA5@;hVbjHi8`1$6}T=UUHQS)lc*|AnSn;gJ{g zX%5b(&yJxpp7vP^>R5U9PMb}jg?xJTz3ADH)$}{H^mbnhp81SK4b5ZJBKcef@&#YV zd{hnF6Y;OVUz3)Tw%Pk}P3O^lJj*eCT={$s)ZrJHskWK#^ACKwRCKPs^IrqwcOqK6 z`?KlB*=)L8NA!gbmQ^l^F>exciZ&0rQ(AaLRMjZYDH=?v9DI=T*esnGoF0D1nOv< zo;pou;!gGcHFMj3zLcM&F4|X=!h8OVsG#|US|radgM5Bf2j4PZ`yUTg+#a`y{JQPE zIz-gGfoC}^$CYPSKpnxdReUE`M-rs&?pe~i{?YSWa{dida z53MS$uV~PIKWV#<&?Zx7-s6C!@p$sA9Mln-)R4JHv!>B>p_S(jsSe85h8lmv;xXbj zo;PYjypWpCO{rxqQ(|k^Az4gV-`v$V=dG<3%Z-&-8YLWWC4)XObPqM39@$93x z(AiFvq2Ai*&8l+2c|7=*@ID%^{e}vVzaV0p`sc>iD*2O$iD9=TPE)S;MQH^;4h321U+*`&-KXXDr1jl(NFC%8 zs6;Z^Hl`P(tD?Z|Hr43gM9ID0enee z#$hB%5L^b7i&}(#8}wg5XM?4hj^*az`ZLp(Ij#&fPH2Ds`Xi(U#)n7<3JRsusK$|0 z2AcT?<)Rkh-vRySqrARtUTdwT`gU2E!lKxKDMr7P7W!lnPm5B9|ek*TzC@b7`~xvE6C8!i|vZI~f* zGgRI1i``&iPTa!L`|6CeA^Y>p`}-hQ?um`Wwb>Ko#MAY+QT$WeE3Ry!4SGQuVBCGl zetvkrP)aa?HZr9~LcHLQE^!@)O`$^#D(uq3L#Ml zG!j0H(YD6Yc>wB6*j7^{{VaNe`l*LI=TGU}@Mc%=$sxjrWM0`a-KPTO>6yMoI;5{p`n3zI6aMtROay&7QY(gLxTs&a+S8-Kee}B z$qs$fnt#K@x!fL79Tg`QpPBw>+)<0f{~^e=Z2go~+hkuSo-s=%*nfMtm$}l1M@pmH z6)BuViSi{xjRYM#?`uH5aOiXSDDT(zv|DGceq(p$M#Ark{2BG5`vpG|T%+UbcOJof zj(!I>{a;LEzjlH5rJzNhBwQYM{ev#eKR>!%`H>^X+iq(?|8+kb1_y0-+icV|L(-zm zx%s=RuZ_dL(Q$_l5%C0HUuJmFc%v4H`(uzR-+XV)imr(Qlok8kXB{rL`7zlYn4UfV05y1TS}^XNQhazjEw;p03KTBj%% zwFv(S$kz-XzNjU)xay=(+gjUx{Ux5wzR$9?M#r1U4+{(8Oc<1lT7>@;XO87;JeL12K>h@M0{=^s z);CA3Bg=v-u6Z3bJAOXr7gzr&gboZBPLo7Ep$O7mu2aJWh2mj8^goN-o*h6v3Y)FS!Q z0P^`B8762MXoUTiN&XNe@BM;r+lu_9jok7Rp8gR7@r=6)ECrN{T7=&S@`WDq+vz=8 z{W$)uII(HLt3ww%W@$JYK$F3Ko{>K+K7}-T;6?eUMRb}#9mPJM1_#S4`LCi4LJBS1 zLkAWbN}b;XO~lZFCslZIh%YG;jTUCUz6N#F@>d_vm6AJ74}JM7S2j$ffZFd>_>*gW zFyaO4pbDt}s71>02IPy$Y>g}39kub~Rfo4R{5i#ot&QCtK7e$%mV-ix!iSNGflLXM zi&}*L7UTaxmN3)Rz>_1gODoLVTYSvZM1KQv^dv0=|2M%}X!^BL~) zv=R5MT(c+h`_rv9O7?4#67sm#Z)h;Q_QKOBB$f(Vuc$@pchtQZ1A^3~Yuoh&dOjXC z`DBqRkT6H#b8b1;`epLj0YbT`Mfj~?d?ss7dB0s||B-;)*8=$dvR6BOr2l@G=gw!( z&EfE1#s4G7=a;0PY?@Fcbmrru4KWh)db514v<#U*dR+29lpaFEhx#yX6`^Z89y*^u z9f>mhNzXk?Uv^)yD@w49SMr)CFt=*+=y^J`Ukay^A{oOn5eyg_C)6VGY6EqYpvxz3 zi|-JXNuIDvQlX!4JL=Ab??$8Ni-->A@Q)R*c93t9ap!rpq`rttOnb|(yPIu#P5U$X z7GUtHBnmw7MXDV28?}i49Uy=5DW_wX*T_z8h;~R{7k;3{Thdjg1kbI$eF>!CP>u$N za#4%$J3+qOwz#+XPG>Blq|RBm>AyX(u;Zp^v>Lbm)R#mh!q?Z)FS*ZAYbn0VrpA0^`x4`80fPBe^S7{$)y>Pv8 zVpXD-(#pU0eSEG%=GNc&5vVjgtEDkz(72!$iO*M%Um|*^Z|litXByt%;>;)?Ui|C3 z@$B9*Og={oL%FC$_}@Ujxcj07DMyWaZZ048oVqdQ`x6fclME#Q*Og-&ApPQPah?Tjx#o z#Y}A8BBgv;P3VdfxBe8KJJLuLyw9jAzG zgEP%soe*`qET;d@fXUM9B^8rw98bL9szZgR-~S)(u*&lb)R8;!;)E|nb;COU>mfe2 z8hdBN-n|eqi(5PQgLTFcuK|!RZxFV<=fq;GQcHPh!fQ*zGuj7*)-B?;{`V)c=xE-e z7Rld1kjtOA?fcwMlgvZ!R6I+{RzL80xb}_0IY|Hi^8tSnJXs=8@T_4PPiF02VCL0t zP)B;xZ)bUH^+&gMKCfybUH|xg&DrPsPjQQnKLy_F9s9oOACNCGQHmt(xVhfF&D?x( zTE)J-EAmrIpL5%H@u!oScr-4kMdC9Aas^^e?@LuprLS5YTI;g)%E7Y%S)1#%x%Gbm z1S-5G#hfnj;{OHtO8tEbp7ZXdCVq_czUjT+>{@(LQ0*aZaS0#=jVCU{AYb5i`-4nH z@3&$v7c|$cy|=US)s~}AeYy3o0W?||o}&{)RZ zFt_<5IfP0hM36=bfclME#D77MFLqwA=Cu3i?hVi4EQg5$KP}t$M=d?ft=$HNvPLtM zhgyUy1agIK)*Gg*b?*4(n|meIPgf=xXX4;!&aGa9Lg9IlAH3K>5(nj?7U2tnd?o9{ zi7&;yFTFgY?wo8)IxauqhK{{Bx3~mRncGGqODL3!T7)kG@hPDr!e<;+FR?p8~H8MmAn{_=dye@R0XQ3iL)zQu_SI7M8JZ|w}d;miZ@e4(B6ZIRli2vds zU(jhywafiEXH7kRMrY65`f`=qOrPQ|Zt-F8sbN7Q$&YeTi|{2tKL07T*qVVK>G7ox z&NVHc^Lz5=$G6w?bITvrT+4?*MOIEI7qtjq66A|^^SRtoEGTGs?!PIaC86EG@}9>O zeQZ8^=Mm+i7U4^Qd@=Grk>oW~?e*UtxNnD>xb@Vq_3`-%+{P`I|Llbm%0(^0mj?NK zlsCIFOG+ig{axf*HXbVsO}g3PIfvVRAd3(0hMRrF1@`y;Ggs*3A5XV ze0O5@T`;|~7&}hvR7L$qEm96ykgt%5zp_&_DREzAnv$>n*S9jk+nnthxvdYFmVeIt)P66ce5znfA3wdQ#(eSSsL~aszB}CZ zCxQdnv4_KhF>l5xf_y<*uKRj@uW5dSn=#deO2*40t{gX%<~FYl4rc`)jR$IxxF~^K z;h2z!V)+#-PW_UM^UKA>$LO6yrMmk~TlRBs)V819L@x6u0 z`vbD>KXo9*ro~rZMHh1$cbK;V*vl9!{)AEd2MGy<9fl!Eo9^Sj3RWk)Xxp!7IgeYv z8b%@oaux%P4{DJ(X^i3vU;G}_BR^SJ#m%E}lJ1{$J-+7Z|9#)b$lEXq9qv;ZwLwCs zyu9M13F-)Ma_en6pVHZ)vCWk}tCDXg{i=p{Ah&*lsWW!Jrv>s=-q9m{=Dsfa@bjn!o5`-?+6m3W?y$IRiuU2en8Z>4ALHu2r{7b&lVYc`q9CT_bprA!cVPqw<)RkhPXzf&wt*(e&h2-n>VFUWM6kp!@>89;{WrJvOd%0T-TT2yjL@{>r7aYNg8o@xZaLCpV3#aYXIe<7U53?`8c^x z6&F{`B?Ls$N-UoXTAVwb=lQCJTRVWaZ9?JAp^-x=l#5z~ZvgW7E~}b*zdd6Z7W67{ z(ynB_!c}Wr-Tk@e(MCE6UaF0=zc2)KCTvamp>sh&$6%$mrgZ5)iMGDmTmA}hj}wJN z894>PY!5~tUv1%r^3QjYFN@a=h*t5xn)WNxv`Qe9TYpL+QAr%bGgdpmgM5jte>FOd zEiSC8t;(MA-d(dTeaTB}Rc_-NJe6VH2}JhL(Dk`=fAp)PcunB$ZGuY7xE($QRz5RabE9$@f@^OZ?xW!>4#Beic5kk$e2% zGc=9>p2K5)Uh~LvdzEs7`ES;@J25VlPP&x5t z#>v<~uUqHjxy7By=V-zx7qtlA9OR1xsPsB{b>z74+5F6rH!~OcaqAF{>pU3Vy@V5! zKC;J%a#4%$EkHiM#XmdgJ;|%E=vbptdFh7Fx^akak7U9nT`FzV;r>s8x*G%FLxwdpx(M0R?+=#Yh5uXTnK00z%gZhnH#Q&KfUwEdH zU0s9T1}_ggY5yiqaXX<6zmN5En`bfN!ya8Q`^{M(U(+RK*{dbhMfTs`CY0VGiQeSj z_jO}m$ndg%(Xe$@~~vEUg|~X6{|pQ{SLf)Nu#5S z8ti$HHOS|4r;FVBh&Os6c3*dBOTkG$TlLM$?{lqR_z{{gD%^m1?-R`d`D#|vzxMnw zbw5q6;|n-+*D8Z()A_ZUYySX$OAzWyAGx50#s#%VeCC3DPs{J7a;xLZix11++}7$g zn>ZA4vO}NS{wp<{b@>40p%&rJ1G&O<9qDHNBSx=0!VkyOT_O(}tg`s_zxP8J>lkWO zuuo_ZnaG)&Jm#S`ppI6pz=rc~hQ&4x!@^a^H>MB=Yx8ELaq}PD{XqRjE#ki|$Q7+G zY+RZ0^mE0=4l6!?omay5d%NzExwR|CnHcBJ2g*e)!nXtYl7U6O{58p!wO@bRU%El^ z)&)}qO4bm!yk~3)aik?yzc3%<^R3BVdv&;^Kc)XWzTLi2c<&K=xzq*R`Zan8V^Cpl z&Y^yz7V&=p$QOhxpYm^Umr=7nXx?h*KlRl*{$BmZ-1a39{{K!^Q9f!B9eYs6*j(dK z{0Y8i{z3wq+CERc@X+VWr~T`>%@65ezKqNM96cy=8Gse9g`kc^ROZZIf@jYbht^3v zQ+r0X)GxN{59Zd65uK5q6!jmqNI4dPd;ziP&o)cMNjYuO5LvDu>e;SUT-tUWn)3g1 zR)o*GO9U4O-5E#us6})fKpno`Yp)3O9U@K=et%QN5Bpf3UUtKXpKHB@1%<*ZO9JCs zG{?1Ntav$sI{YP9J|23!SoPQ|5sOs|_vqiD-fqv*OB?M>D$YbPWH~>)~>?|tQ%J-548x_3FPuWuOcW-d-?+RBK9&Q+0tb_ z+cI8!3pc;vSt!{LzW<3Xnourk5&ja8&nHv9HKy;n--jzxhU#a}?H>;&wj0d>#0~nvx5JLQs?I)CrT7N;;b&uhZt;nrkm2Uc?paYTY7zc2kiX=_g3r@C9baFU@<=S7VkWG;YNw3n z0HpZ8b~ZATKzXP|xXVGV?SfYqWbK|+&A;xp{+l(mQnc*;yuRaH#$onjbBF@UMJ>X2 z1^KETQ$OuHbWYd9VuN9~Qq7BpBs22%a4zc?Vi4>40#?4P0J*>X9hGy$px;lsnzY^t z&UkdaJrg1;FYsRZo#)=E&v=&#}HyY>A=(P|n8q$Rxdb&UsGm)dwx_p_PXaL(PuKPT;|gO@ONLF!#-9$c!GQdw^h^Bj1$V<+tPyf z-IU<8{QH$`sK>md6 zBJ%k^cfVHWe%x#Cm0or8s$8~`06}-^*@LZHRksPxHX`TRe+zUKtK>F z^a&JsN*^zNaJ6cxiaD2l0&Qw|^icuSf7Bx7SPSyqq#d5xFWy_Xf5oTP>fgBYukVrz z_h@m+Pn17W!zdTE2!9>OcfOsoc&U9SR21agd`=-gAmT^vL&g8Si-IOUW516^;yi7_ z%p-4DhvPduzC#wLOz&U)SE>Adc~+!Zv_Z%0?hPA8*EhVRq=h0=I%)vV`5XZ(hko#U zj}K&dt&&WB{}qcrDrvPu4!4Spo(D1cCh(pO{7@x11P`a}NVu@tn-8cXwqT{B=&(q$ zl~}%s;g2}!+4D>F<~fbNKgZHx?X=?wjC~^C5h=7jP>a+H5!6vR|4gC&<{_ySxq&}- zA9b#kJ9X{Mkp{>d9M=FHKT2pY96R{ZX$T>RbLWC*9DPAu@edA_^nmBKyYYS^KYaQw zhwydTi(Q9IF?5;VYcqwBX)L1~W5pCLA8L_ul0ZHA(rL4o&wbMJcsDJR@-wM`E<8(6 z`~WnayB;wVo<+e)NTJZfXe3{}Pf(~2=N&voNl`u2BD#K{t`^iTxy!7*bY1FDSY=`8 zBH~1$wI0VIBksD49ZDpk@UnGW=O_N4p2VHtxmUWO_YrwR9mOAT&!dz733p9}%(&@M zN$`FvJYI(Q<7uJr_eh8`a*-Y_7iy96!L5b!o|`twW*Tj~$l_a5Xuto3{gn|X92}UY z08Qhr2X6y18Uwr`$oQZE&x|tT{vNw^V8)dU>MGZeW72jeN*xSXX5ZxXJaR$l)JQrW zGUTSqkuvZ_bwY61$X*7IdfWi&iRieP&}#_(MC$`xs`1Y*kNDcb*y_ZRJsp)O3Q4B zs19loy&zCer09=iOkv<&UFCt^zWVElD#3x@-YgkC?vl-{M&?u~548w480K>J!}Zhq zY<7e^O=vdD37#jOkXd%5`@^Bp^ET$)zMvq+OAL7Uyc0e~LVIJ(JO~-3Go&;9WRKQT z_mdB@=SMk~hreb@YBsFah4|fN#c|8PAx8zWIQ1QH$`yK)#Ts!(&=>U(<_yFYTzF?q3u`(qGn@ z{*TYRch7MMhTZ>c1o^@%peRxJ$~|?`#~`<;L(rQ@$61R8N5==bOEr%7HgFV>FEn2+ ze898hLr#~?#$eSt_u&w$grP~J>lw*W8spgne=_=|4l_-$`cW#VBX#VyhNH(u=W~4d za@u(lXIyPrc63+E==EI~g+!x8Aum86b2&8rs73052J-oPgZ&;XxvlA^o1{$`n3f{& zu656jn;3lHWd@XsT7*vr`4Us1lh0%ptxuY?x%J7xsw2T#sTvJ$M#qV92FbcB%{U4p zASX&Z;}j0+;45YoY&p<2U5xL(2=zr}*n1IZUa{_I9rP@3)Kdq%`i)3X2a?)#GOejc zP@2DP^6HSHw10Wm*2k|KtwSRb1478~YbeZ@y3v=kd6p*%)Zsrd|8>h@>dHx_N8EDj zE;O4Mous(tj~-vxI)pGXp7Hn!{8cr)4#fMz`xI~|IgasV6R0cvD7IPC_fx%8)i1kF z=#~DQ&`c%452NQ_pso+|#s=C^qWOSYBri6DI*_%7>+#15wn0>p{yXui}z6 zdjAg8@g>1ClaUt_P(ErAooG-;;hlErL=96u|3x#kj$JyR@~JK(wfZ`+-}tTW3&!@mxHx3g5R7$s|(dZEut3- z>WMcUI`Fxuxijx;te>#alI3;(OrdK95FVTdfO_!Q{J-54%<(1;)Pt5B&#lUI_FQ{G zT~+wx&)R1%U&&jC z@udhm(@{QZ5uJEYM`cmx$^&}~bP~(zPw#4<65rQ9Ph^r6@Aw74wZvG*!kaB&Y(Z29 zwTNBV3JC<^Bu*bfi79dF#O|DCRB_xCM{upcc{FI!f=_+2}j1 z8ND;JOb0(da$UGJ{G8IC?T`Vl{9x+fsez>N>{o4r^*HA}#N2tdW>-9YMNT(pXs%B) zTpo}}@8(@^ta703mP@?!ZIaFWGrI*Q=gc}PvnR7A-BnpdgLl6H_lkjI z-uK=P>fqe@uSsP{J|zS~gUbe{5x1`KxOqa1cYU#rWbgzckwj&0sG#LREmE!>pq@(o ziY#K;p!UFz#f|z?g=l!x!&m__$|oGpisk* zAbqt-@yWH(-;;y=$3{B)s|d_~BMH=zcr83zw_(wR4VujStK5QFgrMZ*{r&P*8Fa1kD}#I{@h+BeP#5zdbCbBnaUU~BM3o(X!naX zFWU|3DGt+5t##CMxf|edqUza?y%6r~n(NJ@*DIs+=!^y(3{RRlFPNg`LM>9hWKa*f zu#}kZ98=nUG4ekDk}2-x87IDa%;a5P!1WWXa;1Pe8u>+jsor=)wYcB+*4fHF4Bq9S za@LS{eNkx?ANpALAW}gc=@V0Dm#x(f+4wVdZ=m@@{T{(qy9^!P@q=|33qR%qZR`&k z(YT=&iQgVj54za;c2I7cT-HmoO#uc|YtH(lZtry;y&nPA7h_=w<_2R1hw7jf(c3#p zZ?{ioSBVB`xz_%9wF%8ncV;X$FLi}Xc=f-L%unK(J4}8f*BY?&(m*}MqbmGuE(^Vn z4SXARmA>^i<4JY%&kF2$8|#dk^Tjx{Jg7y=wGY%;G)HFc#l|Fy+|*dyqJ84VI?2UF zO@r9=#?%QQ(J71>4w2m}V#YBY)Z-7$t>5jiDlR8B!Q^g(rucpLmqc|wXew45nR@VV z_s~L#$YU*Nc~Fa#Yd@%?(-`=U-)Yv*H7Pr_deTSLjjt@cB;}x_oN0^^?q^B8R}i%?F1QPm1pFZ8vJlh81qMS(3uWe9@HY`$^`X9 zq*m@Do^|k+KNMu%m~f-f!r+*!)UwgvxwG@08o*dZa9%t@`KU#74uCo`nG>sQq0%Vv z4|=8xU2vawgse3kI*46Qa8nM455`!#ILsoUeAFU3S)h(c-Ll0Ug|i>py{hhV7bm?P zRv@SEI5v9UfKD0tif<(4P%df_em2M#wf${TN!z&pRM%jK+yWoDoZZ(Cw%3o|7h>?o zsE30f7k9`!$22W3s4+@y&0X>InX7mHS$8P&e}2Oogm`#-pu1)h$;U_-nQUJh(n#h{ zrO$u;CCQvJ_bw#t=(57ib_sk50CL!;0q^OsEz7T4qwaGRg}GKnt1X(i^3CHlsz=pt zK5>=)^>#8O!u};Fv|Ok~%GU<^BSLkHx4Wx{FM93k#p_s|ZZh+Tnj6aOu zal2Iuas-PWmB}RUpTBsJArX8aIPQfF}B*)_uPQ(F;MyE<#iCRxmXNmqWu=4~Q} z@#b}d{%NWVSX}(6wdCjX%X4(zHKt`f{5o580FFCytp|=fcfWc-4u7P$;`!zC{|()q z;rp>IpvT@&bwZ^mn-jpDlZ`tBa)kcn&1$jgQ0e=ieqVa~()-#=LytM1g2V`vU=z5n zHbV->s6TFgW#jTd9&X=ES6`xI-64Lnh_;-f`IfE)k+(WIc*vA2 z>99`bRrBJFS}bCi4TfWCZ6Qunzd# zFv5X7!YEfi$kCD;nwR%X**9jjmOQ_@#TKP**Cfh3W=Mds_S%<9MD{Q`No8${jb4<=>2cG3$+)MW|oF)yNGR z0lP~e=@BmT)l~)*quhf44-&hpyI^(AkZaVBw9^}YCwl1!x!kv(4#|%2#_D%|gS@}= z-GQQI;(HF&=3mxbYF4%W+LoF2-`M`KUM1z>-ye`SXMWkyl1+iL6(yQd_|FLKOhAnqjmm6*7;IUpEg&o9*-(QfYw1#F| zxS{sO-v)ujHlfb+ppIkT{1&k5dE}MRvFdpkTwi zHAOalDIVNAyHPx*Fn3Y!t@s@8N%yTF6@brvO%yZE)i{uAb=`KtefdhSlWw|dFYqtV zoH`w&V||Dt&gko*WBJPma%EZ^ZcgnJTPO6_bGOC4fEDWmitDrk+4;(P6_Www9^Yzy zkT)}|W&1I4+jt4nV?T9MPPtem#+=#qf|JL11(OF)0OU!jZ#rn2)8aPg#Ix84nXd`@ zT9Xo5iXg?2crqVZWL>Ld6&=G*L694^^4F~u`7%Mqc3bC2MpIT^zGV62U^=@UAdfe~ zNDSVn`J%zww7>VV)~5RYey=O4>|B2ezcV8J6DN=PAt5$T81zs4O4>yST4Z+W%xk+o z7S{Mt*Vhdg9fXwG<>q|Nl1Ds6K(1bGm(7x`IV8L03-8wy*!{H@`*Y*1F*}b&UQJ{L z6T99;L9S3tPOtd+SQGay0~x(TG}YI8uK6n4v)e0kYlX21%$oFKbHzZeK)Q0n@^g8g zn$263?LS|CQA=H8dfs&um$i|Nc#KhB;viR|)pku8!DNMQQ&OS`tvr6`I@>MVBO!S- zzD$3aHyhAEU~nZsu6}Raa1mYbaGrc=^zkiiVR1tW2j=aCq&T^}<{QRD~K#lG5nMQxzoPV))&V;HgMSxi(9&2r>cUKuE3+q z99-s$^zi*0co&B!R~qCxEzqAf^X%+4Wy_wWecN76$&hevH&bKR7ssoIyz5H_@Int(gHMgdYn>mPV!kQo6Cf8{~0 zwOYvKrk&3_R;eYsEq+8eW(<|z?A2!H74sE7UOWYmr{?wh4%AY*b-mB(q4?6N@^Z;J zJNmXm5~!c>t9wiuW;_)^o?{eIcIw-|(!)Z7syp$zb@%o+uAH}w?H}_jl9zuXdo}EV=ueNPk)WhK(JwDx8b5v@!vFFW<4>(y4V$`!L z$eU%+@gkt-RdJa3U8}QuJP(-=I<%%)v-{_f*SC;TW7dxv$Q9jnM}3=S^tu%N<82{v zVUfWRrxvcb$nNh(_Uq7U$7)9tK(1{1GY|hGzrKj=w!f~~9eA*4ZOgVe6?Qz)*S0X@ zsR8nq?;fOGeV3U`yZ;jJc`ko{dRy^@X&mFz$m?2Tm0uI&s^)L}asCQTbD4LgYuke* z^aCcUI$9gqESN|TfG7F%|nvTh(TZ&V^+ z46ZiF70R&`%#^@q?pwY%_}h;KKkiP>?QOOm&1Igd^5z(cDlvWvp@95ajOjod3yd5a)JZeZT8wgUer{*AH8Ec0sCWf5M6beBS~l zGd|D4%m*WoyV2ln%Y$xn**ybK9@%N`;`eK{t9&BM?)RBoX3k>76%X>1UoItGJd<&4>1o?6Y<1h-**JWF-Cjpt zV?xskt6iCZ+?GH1k}f6FJAT*B+I2X{{1VK2ac{OJdmLe&v!LG~GT9jAHwC$T_7)R^ z`?~h~P#gP; zK%-SZb$(@f^6*b*@BV|X21NEa#eC3{mwy%@Pg#U=yxG)bCuLV}^r!okS=O%pXDj>J z@npYt!-Qh{IUVHML`hh*Myl@h?bwu`dsaICs0Goyi_Bh6z*U3{Eg0pt1bGXB+@9WT zeHv)=rS4nh{g^Czh{Ga7e@-6rwHDY(4BiZoCuR56o;sXTd#I}W#ff_Ls-}~s7Co_? zJm#w-*gPwcr3Bx zQReqMd~lya#nv^CF8@BoZkOmyM6|VIa%X|u>G=oq9lMmpag+YO{c!MY!2|886IXgT z>l=MdWQ_Wr4RY}|olQF=b#E`3mfv!`X5&@!2M6)HS8&FYaYvuwEk^mRL7u8*Pr{4d?txyY0z5g})_pl;5`aTf9nny||>Ud#NG2oBDcY@+v-x*4EL)CC!K0xkIRg^7szW12pPj) zJCG|-eDlHX^Zj4H542`FUiCfLK6#$PtVVXf47^srBx7*rgIxWVX}v3;DU{mz~e;yEnA__08|@6NOe@x6HR< z=K;cnyMMHyVQ?Klf2BVam6{Zo%rFR$*&Z$9E~forx0gX0M;ySf=dt792y!Kj`%bTN z&nWxXYZmwGsY{Vv#FlBBKC{~y+-r?|bT0m?ze^frwhCH9%$V&l#$q&y@6`73aNIUX6q)}TW1Kf;RXc;6N$;R@r)~@#%ONgxRG6;UW41Ez zR73=Po-@Y2d^LRZ&zbjp)DLIgu76pgkZ>*d2(@ZOTe*W^)vFN>=DASyQjjzKp?7M> z=CXSWE!2G_Ob+ZKr34NQJ%J>M@Gm7GU$gU$(=w1JAXm3LN`!Xr*v}+!O9{Q`+WNx$ zIUfNY>>J|`M%=jSD3?jzhcSD z;hYzMP<+RlCln zNLhVIoE!Q~uC?~%?JYGZhe!N9K+Xo|!&{ErZ#_mh`&8A>XJt}=GeIRv7LtPZ;o-uO zSidI2%rp$YJVBmD^YpC+b326=HMBK9nLPgsojzlI<5EZlx&O&=i4lXh3gj&?exJSSKUa%9~inlp78t0!0f*=0ERFuw9`_|X#hehR|D zYM0r#BOqs5h?Yx-d_Tp+F9iOL5%|{$;fQf9UpDS2 z$P?f5Xztx88>w`Qg6JRLHW5z5O?Ez+_&*-De+3|qzu@9S1(WXYr==vin`LB+t-MJ% z>37WfWcn8xMhaoI01W?*fjmB(Q0~gL$6}8J^c2k)y7Ox1RHs4XpO73JHwG^No*%(K zKZm3)vmsz`3qh{@#l8hulTwx#NL9#3b;qY161W&DOc>$9sYmk;fxl8kTPtQfkAqzG zVw=bzk%))cRXVPx-v(~V`x;g>a17*z(cl*&klquMdjjP0<p!#Z=yxEend*X(a zXV;&6ig$*jkvIgCg5m!#p%~>q3G&wFMHrPVms~qp`kncOUoZbf4gRp)EI3k6!SIFO z2%hy+1oD*Cr;6o;Cg2C7wq5;2Ubd^)(l+lb-zXlVTfoZ8Qy>q@vZ%k1rBa#{c&IGL zP<}_RUCp(6!;!oU4)mpwf|<30QQl&Z$M>bWK3LgqRiD9J1LIQaqSfWg!xr3tB+>Z6 zKe~Zb2?p;p$itN#NIvaStr>W3KyhY+o9?w4ANQ**A1N>VVIaYmXZ%V)oefUU;ccWce(x2#AI!#`0eF1)f*t4QE+lV0xVEvVW9Ec{^>MK# zKglCJ3dtY-O-o+9vj7iT^daQZQUyNA(xc*qnay6F=kL2J#wm^B@v7fakjF1KOZW3; zy5^to6w2A7gK>?-_flQ4Bl!$pp(ll~w&XDCqYUH;XnKnJecE8L^6CNc*staa)|$B< z^%LN6oYhZIL&D%Y>@>#Y8H0BYTMLHG(;a5J*faFmgjY5QP zBEh{6D~Q;6eIDdOzicMnU2m#+BgXTz*S0s;$a=Py9J@#I8U_Xt!NGj|b>2F{jEtDQiK&G;g2Q>AyhIW zm|S^T)m|WP1GM7XV&M%Gdgijd-SrO*EJ#L6-J=-e=@{qjYe0@b)8%l53^Tqpi9h|; z9?%RCHj$`wNN4m5OinN%(3l*;2pg7PYeAmP?dU1)U#pD@*I9p?S2uz3?=rv2uep-5Rs>f2h-;4eoPoJU_dv5h`+rZkZtJXoHFfWAhGs>9xd!sfIsR^1(ruG1Yy)qHm(BX)16Hl!c<~?Tj$oBzR@byEC{pAkMo2kqkKmD4k9tW zdpyM)b(?=EoP7nf{r4|7M*!{g~ddbV!}869RvEN)M$| zO^9UH!wt+V!z{-wkT0_N%FpAXXBRG=HE?O-#GuN&^V1*4KZT}n;lu9$(csrTSg)58 z$?&hA!z*)4ojah8hUnh*y;8xO{+W~vmJ19tCQvQXZ6`oeIdm8sl;{sbn85#@^Y7UC zc^A~dRUJ?%9tci;9^CZ)b4Q)tj8?CGQL7*ZkC~sNxJc!ITCAJ(7B8a z3(}qazUfapw+PlB z48N;D{!r8Hx(U~BDSUc)OKpPr*Q2j0W{1Dq2`T)KZ;X;LxbVd|&h@a?`YZi6)`;~x z|7m}3Ik?ScsqUHM@urY6$PFhlzv*G3F}O7_m!qH1xT6-HbZn3C@ovL1_h0rmKdT;* znhXD4>j;QN%I5l>`^{7)rRgb<}32j-Vjp0A* zk`8bGpMZQmSH%OJKjy82R5L`}XC8Ct)v=MeNZ`nK&iaKftsrnXX;dKdZu=nmwWj>-TqHtcd5`&X5m?d5*^~K)#CkQnGeh`|N)6x~#yX)i<8+ zzVjf_3X{(~T;$1r3Gzju^5Kg{PvuK?3IBVVrjz#ly7Gyy@b4CKtq0a&Bk%aU0{LP& zQtvC0Qfn?xdTO3%v)iTq`UUx){E!wFKQsuw1i@6m$fE|3FB{7PV+l1*CIdb z2>h@~t2b4~Xvgs3CgZS>RR9crBgiMLJ2Lyy*)Ee$Ef*(d=yfIgG<;HS-U;b(^&fsZ z8~K44RuwP@7z~{zSmzk~`z%G=##0R!yz1}&%r$DI1)ZOor9;Uzz-SL_9rR;4>~{Sc zERXcH&L2mooLyv;ZW7exeRTE5+y>ioGuiVlQRw3507#%zWd~u zFA3u+X}|I6Yc^bny|ykP*)BSG(Y0km?7Zco1AoE?>%c1o)*p=Ww1GOAHcPVKUFA>C zw`d5Y)U?{om?L*4xspBKgJ-DBRUu+wGzPaFtOx$t5qA!?3jV&nJoTNBxRjqo_EjLq)^{nos`nP1!GX(B`qEc8_Qwq>{L>wqT??0ODhyjjSYq?(NU6A~K? z|38oNe^c7Pu&>N5qR6E;AES@%S)!h3dH))FoEV{U@OiFhxKWI6;{|H^++pFcW#DOcQ)!+$9)4fuG$^ zAbj}kWdi*6GTtA4+!6kCkVJ{Xs^1=vuP)MbxvKR@LXO$@GO4p2H|nH?Hm?@sh(F^Q zVH0@M39bTo@CjzO0F3y51^M{6r@PHIo8VG3)K+IkoTxaPYi(`>}htrj&R|);L*P@GZTaV1LVt{%J%n+YrFBSF8Xe#g@1+e z;ZV=gY3zDMk<38`{R^*m1Ac;he(Lwq-Afm*J9@BE&f7!zx1Xl$xx*-)0Kg&*GY|JzNN3325Ghh2*U5@^apKn5wYX648(34Y| z<7>{TC>KAJ5c&+MqPnc9DwW}fADKA|z=+oX$mP5DA@0e}8|u%y?l#E=drx~T(~x!G zG&_$NzrKPjI^jhX{KgIAkt|+$Gzjwfr#dAXPh44keqw8dQj75exj(0GT9mT)C0KmM ztz~9lu=l-wgIqQFz7v;>8hZ;4$nMUFG7`A+OFB(Dj~x#dmvIY}`AZF9L0Ii}2-e6}9Nch3vE?W<@mZg&)$2gRN`GuO>-Tc3sSjm&4*W78eNQ7 zFTZ|f=U;X^W^k!d@bk@)jFtv(I|jpVoWTDQE8qF}!T1RKMy>jMmG9Z^^QO^mlN25< z@pf$Pk!FuC5uubo_^JT28HbP}@W|ic5^#-6R<$=67c2kBuiaZDoqdy1=BH%rEmdz3 zsjIbQ?n5)z92oo1S=H-7jzID!XT{Uq^o&Pk3VNl@avE0!)t>%{#XV0eO56Db)^fOB;uZ*YOSE zE6+Z!iOc2Rz{z8^Vod*FInH*$AJrrDCC;Jud56k*ek%Dwi~De_S37(^f)*9V^E*g* z(Bb3+zq7aLc(?k>^*#M0M+N7YbNf=|RC6E^tr^x476!);Ooe)AH(l!ClP!01pgc!3~Kc|%OV z<=z{;{5g)9Z9adLGm~BB&W(bknYf7r~G!)_dV7$r6EGY&D?x8q1kdF1TKWkr$ckz|hx@EoWx*N0L=8gs72!~mh7##SJ zjgzB$(5$wu<9bv3J5s4t*v4%>*+-l%-+*Ko{xBbB^np*<0~wxS@IpbJqF7tl!176+ zcT)}?3ZX9v$aUKq#|Dc9??a&35k3zu|nZ=dq|#I3FVe>ymLp%i%7 z%kSD2j_VB;9j`#MZeu^nL` zIOhcEkAM6#M2pGJ_OshBK(ZrT3W@Ql3hXFGxv3yedf-v!9bt=`w%ZO!uZXwxn^6~H z|5=`u$BcSmHZg{KSUh;`%~{V&?@oRCqTH;xFYx5WRf2EUM_5-rQi_EH;jxO9c32!b z$Pp>W_pbeVi6TbRP58b@N6*?psrm6G7Khb^V3aQ$43a3VpD z@_Jo^Q!|_r0uoXWxgOm0yJn~9={xa|F#Mwx@Nc*9Xy@?yoipCD781d-{rQ!@*Os6A z5#+SEzIk=|;%Z0|zHLFKgoXq&eguQr+_B=m3FL_>J@&W#5@5KhgqXa!C#XQvq;|#k zcu0&CNg{d^gONKhJn~^P$iux^wKP+H$3zKT8SU!N;*S~%4)}@`zs? z=%02_RIFL#8U4cxoQ;3S&hR7|JV{D<1qqG$o$wZr!#|uY)uK23=m+xLY2_ykC*|sG zn`e-ZaCr6O@uSLDel*S6!02h0V!TI5!H4Eceo2N@CrI9(6hdOWDUFA7g!!=K$Ob7! z+!8?UI_IYF)b`VNjD5F!%lCgTs+U2ip9%loBV!p%@b$&BelUbUq%l4^WbRgDaJPb7 zGye@IRi{V|=GIj7{B4=hdHBrSP@D`T%H#$!77`eoZ6If_V%CrDPkzU1B=#D0EXw%R zyl2;+HgmY#4F4E!%LIopZgh>ckDgVX2y*c!tWRsdj(Y!5q(G;%^BHBCYGT2?97vME z^&wLkS3-ELH?yj@!#s}ln_hHXQf=R=-7_*NixW?U#nJfe71z9hWEnixT_-%92LBW= zX1RBO{ux;WAC~$ym?UqZTbInw*EXkEu_*fmq{8HdQ%RAGVk65NR^c(?xfA4)SAFnv z`6e&&SNhW0Md#Luwa+4*^6`P>5bpoOiB(TY02g;}d+ABR%AG$Ce$UgIbW!2eyqRfJ z*7l5WSw9@YSxs2nT>uxFTBI^X_o$1`()P|-`2Ia_3*OpXObUh+82-Xv@4`D{=r7rz ziSLm0NT1ox5zBnXEt^je=5o{*MDX2uZo8)X8U8Z3T;3bx&J_mzHJY_De9f*| z$Ipiu>Pe*zTN_;16hejHM?tyln-Ca&iU5A%bT25JYdlq}F-2sl>I%{Y3tG(gx7BOe zNZIfD2j#8TM8+ne!^396M_%(XS9g$)ES#A z4st`%FMTw+zNh6_uBrU=U&(j&t2e&L3In;U8yhs+a>gr$1U_ zeq>Jn#|Z}@B~E{lH=+qCuiL ze*2vBdhyyPo9^fP^T+#j@B7}zbFIDh+H3E#_OMjS(kG@nt?G1=OckrOmHPFwcvL=E z@q<&Q_c<(e!EF=EHyTNd%BQWY$I)L9<}3XvvfvZ)NuAv6 z#4}Gita}>oH63ZAk#UIoAz2I)R0}0M24Qz0n5(tv)~&6Hl3{*z5$RF$?^`YDQfnL1 zz$6*%PVZ0;aO|C~H8Oq(!~6-&PNw>-3SJilqFqk=Y%UgvDKxpQLgj~sM)-R8fmGV~ zd=Z%M+N7Eg>*?%OT*RixH^FtUtjYseDIBLD**^%-0jC-y|jCz2*Dj zD{(g8CaiFK@_9$&7ffPQel&ynsQ4-h^96a^rp*(dJ?-s0FaJAxFF%ca*l_-PC>#g8 zgFNx~>&K1*Vla1h&)fn5&82pUA+Z&9Ov~m(sre0X+=Al(ZMvm_5%!n_bA=LWgv@QP zuFP!>i#MJ*6y&_^)7wlB*bhNYG$ItQZ8er3io;yqkfYn`%T#u3@;>69;Jjr1rgL*n zzl|r?2lzaL`!~o*d6Kora4T=1CA4ls=_>_umAE&spJ~}JAy?$) z7SpH=S>KOnUGP5y+rc-)JJg4GAAbxxOoq8!nNhw>OJCc)vMKWAOZoCMc*g0QlLKKt zBQMO1<7d(^mtRZW`lpM;va3_OR;JwP|DZnA*@}50><_fDq7>i_k}oJbOo6$wXAPz_ zL`F4x-P_8S?$Lkgv;Uvmq&={{+;js23azT`2harBjexl{FrN_!{t zp4dHO0qY&sH7?!_n@+ld``V20hb#j{*BCJ_ro&vJR{kx|{^Q#Ct30aU*^<4_PTfDe zR#yz>((68u9*p41!rUpAH_nS}$+R&)(RM6zl@@tKL^5q6mcbD32h{V-%*E-^<>eK`%nHvgdu!x6 zKkZxWM$4B<9IE ztnf1xyVW1=>!`PA#NIT%C0^z}3{PmF?5zZI!)jw5Z;L7L%UG%I_Vv%wmGxy44BosQ zSvS$RBcU~fv`zf~OAiu5C@Jcp!a5@WGki+cb>-JmuKoyKnVvi02nR77v_5 zV;s+$H*QqT`nD+}^~~NJS;2I(uF}qkf4N{Vl>xmT|@Z8%_i)$X)--RNA>&`=8azqzbnV0QYwb?XqdX3fhRQ1D8*UyWYG`gL@jz4g4 z1lIs?8S=)gXDzGO+WQLg*QV{=*;Q0I^U=~6lZ_+aLyY|j)Wr|40qYR?%hCPP5Y}5{ z)5NWsIG0TO4vkO$sIkVUG0dyc7rYZ!<$kmN^HJx9vZWJa*v{BkwL?78yeRw5g?V?p zX1;kSyUK;*BH!SwXPx0|UNrLL^Fq8p&$0BI2lIG}&t0oFZvSP-dj5*!jCTjZLM(G$ z+#6Ysg8m8%RSS+7IgpI-gZVJG%ed9C^u_T^;oC<8)TSxDENPhFI_ChSXZV{H9ESMxM| zX7Q7GaN0ZpyAP^=%waCmEBmV_%(raR)iQPeb;hx65%X5Juy-T#Jy=WlgvO93v49*X zA9#Bu>8Io`Suf+-AmB5MFY#TRCfakBYD;?E_3zub%X>c8z;gS2*aST05Y>G_Xk1c@ZaQlSn-#}vrBFSZP@E>L(Z2VzJsK2C^#rZj?Ynfa2^!n z3iWXpv}oHJI!EgGeP$lh*2q0WNd*R?Sz@5S?@>RJ7^xiR!*CInrOrx_`8h> z?sw;leSU^>%vShXBxk5bVLZ4$;Z=Bty9vd^^Qa6wz6lMLAEj=#*X~FV(@Z>TnxAl4 z`QRTi4-P6@2Sw6$M26Kwaq;{q16OOmt@^_J$;B!wbr;6fxL3asC~1BfOO7kDLOi^+ zHZC8}pEB_0Je{W`?55YXBf9+3m)?ZLv-VGAxc8C!SF{TBG$f+G@O&u)Pe6Ik+JJ%u z&V_l`^~)rthq*4z`COVuj?-|wZr3XM<_&^}=SdlOeD40bE6&)giZt?Hqf{~X{+SoN zjm^Sz{qXaV^qxZS@VqDkk8K5qo~m_%$%`SC)6ach2)%L|;+qso`ct^?>L~Dpz`ylB zo)2Z<25!0kCnYU<>k-qT7x$NNn7-gXsD5)k$tB+8BTjeI+s+v8=z5cJuMW?HGI04) zTHB`{y7Km+d8&AhINQ~?vD>omo0Im8@K+>D8xlQ4=g@y?JUs8oz!RU^vf|Lg5T~{k z)9SH@%v&yB-7;}V7vshK9-KJ^1!UGDR~_ z^m~4&DJtSrk~ovjq5sl&c%E~F$E@7c7+iHtK4S38-rBW~S94WltC~iE z{d_{dXt1gw?v{^XKRmxV!o&DC>&#nl?C`bSkAF=L$^UJpc2e5ow+uNh!IYs0cKyJ# zG<=y89p@pzX>ISk<{Er&#@K+K^z#d&$6-o7bB6YHOC{Hi zXrrQynww>Zyj(Pn4JlU=aYzZ|JG|+y9U$zN#k>HtmtjAWz2{{Gs}tK6x9V%B_?EJy z3qEMyw zmma~8R^eaoemox5LblS@#hIRuk{d;PZ`8^N@V^rQEn8%%dU2Vaus5#p$4o4L$zYTvj_v_}%4#c`(*pBc5mfp|wUs``J zhPme6U(Y18TYnM$P?S5b$Dvm3UEiiWGLJCKB^R7@4*i$LTLSZB^5rttPnEc}^XQGN zHziWV=9@Q1X~$vwpo_uqQywXEH+p=?Vz!5QMprl4c^#=}>$>Xjd}SVYb>$PAS%01r z{)qF2Z*w5Wiv!HV@}GOJW#{B#@;ctI%I?9B>et|5yhg$waUM`p(a$p)WY9tA34zwB zly*B(xJ{y`cGz!v?lI_HK2KNH&cseH*))Wh59nMX-A#j{$Bz@tmC`!N|324ZOX~v> zb%)>jwukej8}D(sbd^u&2aog8{K~NU&4Pf z>nQst0(RYHubx(B3e7UK#oUZ8&-`&B#xKFU7Fd|89D z88b7Sk{XsB(_XXj@{gz_@r*9O@eQIq>_zW)`Y)~jDf=2jul?C(Ze7a#t!bZT>sHnC zjYfT2C#GOLMBRyzN5awVqwHU}|H!#svc@caR@{ljlU5$!+3Frzp?Cn;Ju)(ksBugl z5=QeV`xd&&f}BNGs!YEu{BN~Pz4NDBF*${M5@;vjksU~HAN`lsPRjlS_sKL@^I7>Z zDPeBHksc3%Ufh&;o)7Bc5uCB?J`?sUvE))m*24RadkgQIoQh0~FgoQTCohC?1o+TT zlG62{|I+lK>^p=DO4q1zP2)Oge7X7V)XzLlg+9(!doi8>aLoyDzg;Z$flQT7}B z{g<#W{M1_2zEZMjr`7>s!$ng*veaO_#9kt}i3Ha1G$36s`Y%mC%6!kcc>~L(th$Q8 zpFuZ%3g()yFy*nzIbnQ6)#$L$py6v2D8HxfS1gN}yMATciS_4~L|#9n6dCZcrG-Zy z<`Lb1;!*Y|gg)1w*LJr|jI&)KdHjIXw0MKnlQiF9{PIU}u8XyQuZ~NRP49TSLBj@E^IS@u2*_ywxy$S=FFWQw+0)W_P+A6 z2FIsuLcfm$uyX)jsP`(yY8%M7M%m9$ZoahrkXe!Wc|YszVWOq_A7#D=9pb}z;36@o zZ%+3Y8sh30R3K zj+u-z)_80Y;ynG+_?*BxJ#NgAezNWFPQ8>)*f~y1@JPYQL;b1#-CdE{&g@zzfDJ$shiQT92Qs=h?46c%%Y9eBz9?73;4&FNLe+eI-!k~dN>1!+IZe#X@4%)bS`y^*Pu zvS)W++wWxjnY;Je6nc9ZJr1ECW#2-?jYmUc%4&%;Lo1=djJA-kMvlLfT``W}RrIH~ z5gdG0oyfw}DGMf;jt3<@v5{8-AavMJ{B z_Zu81!bjuM2iU0G%sR^c3AYNS`eQT9nV4Omt^41HIzOnTp;-~quE zZ?uXe{FR0|ppgj9T-a`c+XgbK70X{7VR!d;I)5wg`QxJ+-)t~mqK*uCW@7C5m9jsg zA2#E>gsI!E(=V>@zQQ)0ds0xV&?t=w5WH|-ACMgPB(J6*^rP&H@EC92p(-^;!FO_% zNQ!|R+vZ+g?>$Q~9vY9a&duoY$pmgM(>=Rwjiq75;g=`r9?+}nx+S)idE-q+-Z=h2 z*%z5G>#hn<`#`yD{sXScCDHFbP3t#dGQ=j(^ot&eD2Vo&!TMpfTE|3=H?H7c9}{Q0 zIBQ#uD8CB0KStry z9#1;wbO7TB^IS<=Nh3ILo(WU(TV3#9f5)7A3S8oSG4uFB>J%S*s>kgW9^tD#B$F8x*@}gsc*(&WF^PaA)wcEMqL_wDX#!b7EKeD_= z`3EImCG=GPb+agVh%{P)_xPcSohO79f_@I+S219q5(S zb`+Rz_?KAsXI;sK?S?65mRN+}=;_jVApE#vk-@gcZ;9C#K)-^#Fq}9<@bX|@{PQP= zVm4t)OdJPw{4j%hN7wbj{KlZ2IL`vl$OYp{fjRI}ENtt<*4S$I;`v9jqslTh3SeuLGywPEBTae#E8! ztlG%$@@WPh-Tg)%A6d*^Fpq6t#7?c6uF0Pk$lR$tzN$7_g!#w@Ilv3^hL_+?dbISUZJh>BEUTq8WJO=*RvO=1u1LtQHXRc20lptB0JqCNjqp z4ZMV!iN}g3NI&cZ6ZnlKLkb9cQSz#s7wSG9|GZQ2=Oqrwh&PMZYSf-9e|4D7!IeUA@O&!6`dTzWUux3O z!L$`k&rH^K9r$oQS<0`@8shDBmBrG z`TIuqjxqA+21D>D>-NlQ1(x?J0ncww{pJ|o|KGaHvpf_VvoT(vpAUJTnyw&%M_I3{ z*tA%SzbTJlHoSdu(l-8;!V~&mUpj#C)898H))PdN5j;0#z-JDF9RjcuI7|yGy#@K;=w6TETxWgRYcJ^X9HYd*$7doSwz872y zmijgS?QR-}Pzb^CfH~{Mze?zpW}OUJ@oQ%#8_TqY{Jb;vrWhyfR0Oa*g0lkV@OE)Y zPG+65&1Qzjvg#{LFEZckZmn?}<^YWl98Z`twMEUy((v?0^DE6i+zqUsT)SE3UG)j) z4A&tXJ?>EAHMZl&86B^|*zy%0SNLgP)|Yx0o_j}Rn1j{_w3u-`o<5|rf3Qw9td471 zkNA>{3){0f7qIJK+~8@>fC%uaINjTk?el?o6GXq-roJ7@I&`@6h!EQ;yBKHr1-2~= zJkpua@xmA8N$Ta5Z1ZVTl-#RTd8CR69@yhzv>(%wU7y=Db+N>&KB=~^<0fc&8}3^X zfbkG#8b&@A(Cr1n7EG3=3#AWmW7grepbIzZte`uc~~yQHG9(!m^WrW z1i`$4&W?>Wi(k0&28PTl=#vQ?xBrN7#H9L z1%=|JYY_bq3UfHs0v9}I8%p@TV)1JCFHg_MFE~>ecoXy|*fsK7n$$O z`o!nq!(E!@Y4a(|LuJrB9pfIZV>#Y9T@CX%oy@B2mqz@_*7~N&+`iZA+w%mBs~+Pe z>Q{~=2hjEkhj~0h6)~|ww|Cdn1bo!B^Le1hs}#}z)>RQ;qsD7w(4%<~Fpp_b;lcp1 z8%Ni8Y}-9!Zh~}XOJJ_4Dg$r4_C~_INs-|k+K-(o_Fi0~+gr2kOZdL*thZoZ0R>FF zqKFrW5d9DZa|8!^3M%L8DeCDM{V?`3*cafSBX_8Mm; z*YZDk%RHJX$t4_&Cs2(bWF=WfY|OY31M^s}*aU{z4Z7axwc_JdJHhYv;|gD;0n8h7 ze8$2&HWL^3Sc!>`p4?s$7w2qZbSq_*>iZ;M|F~fM(A#+HL6MNCNMGOuquMWvnG(mbDJAP?Vu@SguNYWeoV06# z_AKeM(~n@hByZ%j4Uz|p!5BYS%=mm8o*s7!41VYkWl$b@^P|t%n>-kIB>a(!^4~0G zN*te}xl5>JS6A6kXMX9ryP^%wuQ|0%j4{s0;UgW0cH(h-l0FR3zR zwfiYDF{Kf_9|E``pn?aE1-&s0MYo$0*V%FwKI>T~eMjuQ{WPDZhm8{Vj@e$w!1yAS z@fV?pm&nFCf0@NhiR;{-3g@-YKBRZ(q3y<^HTz>>fy$UXx5qJBCZ>nDHCCB>X-s%t6 zKefR72X8l_r*cFDm_Z08z@o+tB~A;3iYCq~zG(aW!_6e7rEYf)W^e4h;)#iqT;i(< z-#AeWR#cDijKd9!@FzT8Gx({pOj!HG+mF=`I?i@F;dbOoimHwyc%u^ZQ=|&`1F!W) z1dvq}5Ij6yGw^utdGQ@9ojUJqaX@uj#Xa4=F#9dXhOh}FPZcbzKrV~$Bm@tS+YCI8 z9GkNLOmBJ$XSSNRUzXc?(_Nosg)1gR^3-roVmx$?wkICP8Mtgu40p2UPAKkB5e{A) z5vc4n*&qW`z$TJhb;Xg$Mrw=fCp?}ra9K0FUrt-U+RMbvu<(7{=JKz{^z{Tf=v)oO zs1Q6!PJDomJ)ZEm&cJ2%>~uLaw^MD2slz)f)`?jqOh zq`q-5SLU4LThrhjhqsxRdG}dZrOvq2tYUhf)?aEWir(Qteq;7mJj~+~-E}o5rs2Bi zj#GPhK7LDDw(h?Ut2|o!)l|W^1YVnm_~IG6{cB(@v&uTDjwUC4ZH-3`HLLGWOyF4J zQ&ob;Q_y}jH9}7iP>B^I2}bzWTA0fs7`n~G;(_j(?YhC|u^NRCRu{fGYia(arcQD_ zi3gtW!3zEm+;uQlb?DBSyxX3a^~=*)G<`~@g$G(UXY8QaL0tu$aUQPUJ+>Xz!(3r5 zmevoz~5>OsUQz||(6{idI*95YU(P`GD*>#RDW{-3Ceht=U*yg{MM%MPg8;_DUQ?;uWt zI}YG!Oz_NGD0udf03-C>26I^#Z`g2krN)!scm7Ou2l@qMTlcql+F;T+7br~Re!yal zSXlG|^(7CGkc@l8y0JvXRC~e3f?7S1^vOd|N;%XA^k@}T?eHU}P`1HXG zJG)<#{im*ik9VLiG&3T&yI?Mh+K*k;i}=jg)8pDGd4_+;Nt5TUq5gZ4rd~GyI~&lhqgm=6&#n^WJ-%pw$*uS zE_O|@KbWQ`7#Hvz$}!^49+=DVBI&91$@TZnU;MA2M0(mT#YwvNTQ^}MxSpDXe?^mX zExq|DefPp#-fE?;9oyJQN%$8{Us7u@lTlS|4n5c%EA7 zyl}3}YrV~ElPyEZc(18~&nI*~S=@+DN5=d8FrQyN{Typve#6Su9FxT_Z9XZp)oIW_ ziu7Ym6%9pDr4r;XR)V_kpvcWw@hubPvOfCuvOx3wmMl-Ngdo6^;gb>Nol5!il61A=Edn~(^6$!fo{lU5y=0}Zd_k%E>TX=QKLT!_S_b%L+ zw)b}BWo>^x<^3jf{lWSd=7aj$-~r~b?4J$uMJMYD>!-#qbh;fL(QuukJB3qXe#j6p zzX1I~zruW?csJ?#2>a*2eC88{)-HiR-W(ph;h!nrAbH&WxO0IqCPUg^bA(SG@E%Ts z4G%BWxIF~RVew*<`~A}Ux3Ik7X%-i*smIIp^csHA`$2m|4o$+i{csrObHo{OZ7Y=) zH!bRVz0qyLv@8oR9=}P%dI#7^hk;K_^doqb{zqUwOV*d#iBpr;%THv#EL?u8^1<7* zP~LP*inNn110Up&h}{pmg%JG!&d4x+pR!u6y^`mw^tozc=Edc|?KP?|V?o9)MEp_1 zzfXtz*)!NHU?pDPmfi#eA3txy@cqlCy|vH(!lUCEM?&3Ruc+zzFu`!g)jXR0!S@NC zQ39?B;t)7;kH76pS081cJebd_5YpH7XWL-ImXlvKoK(aPe2(i;dP0r^d>ura@5q90vV4_be-4QQa=fpKKRc{4h^Kxyv@Gv}3p4zI*y0c`pUnG_tv_dcKBGm%PrwITjxb-*H`Y5S3S3|v9$qLp z|G{!tYm!;d*hEb|$0~3v94z3$h428s0AG627%k@%EQdMkbn3im zMZagB>qsfh(rP!mqQ6hW{F%&RoR z?vKm)6u|!c&v|DPT7QGQ5%nIxxczVj=5y74-EWj}EKBq{>n54%BkWf1mv?KM)A|AA zkHB{%I8qe|J|rU;a=*YlDE-gEd{*B1p6`65v0wTQ>YFPHTQAn+ij-&4_#jR*@}cs- zD1HgdXV)`$%~Z92ZyHb5uWDt5#wzs*aTW!{Jfp4x@=3$?j|l*YCB~IB!v3W&pFcDt zaK`*xa8)g^Lno3w+i|a+L8Uw~?`0N@kf!XWDaGI%4esw3@FM&X~dd30~ie_q`k znxg(e{0;o=EE`zF$o|FW0XiSdWemB@G3;Ls^DSn_C;l2-w92Awd1YSvvU+d5*R}QE ziSH|QJbu#Nen$Ia1)H*S$KRh>-uvsqDTg8u;E0jiCz(ghktjEyoG|`Rlz)V717{U5sOx9^@79v2fC--#=4H$_2K(Qk?I}5Z{>uH)2%)S-lN8?k>aJnL$IZp6x$(_KMSE zhmPcZFI5d*dTT5pwShSlbh^MD5Uo}t?VY=8%l2eauj8G?Tm=Ci%E6ug!g zdsN$E^6UpIEgC)uHVL}ECdUb$p8?++;bbNh!Mg_YSU&~l3T<*RjrG&AjH~@65VL*N zMdk;jf8co;vV=AbtB4m}BCUb+zdD%DIgh(s@dR`IL*{qa9-6N>X|;Z*ro$rA9(aC+ z)RzD=4jS~ldmZL8HJ$mHn>ERBV37UJX&wCri`y5DG^UXLhv#P)`Qf4Di+}j6g4F*8 z%%5~D?n+?$6c)E{@-gC(B|lahp1A)j6+a&ksjh{`rSbNK>S3PfxphK@V!tN_c@}oL z=w@qsb#XW(J;(R+Bh|G553H4G21fMXO_;|!@XpEm@ta`Ry{{vWUvTpBxbsItf{mP~ zv_b#jwO>XV-01e-g89-N^>(ePC$#la_@A$x&LI%M=els7FR@Rqt^@QX??2)`jL`Qs z%;V& z%8ssQ6NTp#S+35i96b4t+xefbkLl#=>eROHAmbU%v$S8N2-X#9iZm*+zwS}E6V_ht z{TOP(>nW&l1ROT1QNDaE<-u@&5nLS|O&4JVG9uuSm( z6MRqZV73oCC$aww`b$+2W{xpln_=#xozp$EXR6&+J2ljMgm=;GD=%|8UN0xUqX{nI zU*J2-d-$O(guX2>7dz;&gzYZ#oV*hu=U?|k%$n2GQdpEsjsslZs7ODWb0K&SU>>*l z;G%NDz-!kwU0vFH+L&A8seD6+HogDV6p8y2L?}S;9>P5SIXf1xRya@k_S>=dN<`@M zm3lYqW^beGr_S)U7=rf*=CMj$TPT03WPxa&dGE>1kJ!wQ3_aGLP4qihj;Vm06F41( zPwAfYggS!z80NAy=gIA!x9*&VX=|H^Q2^&TZOOMe^XXhwlKU^`8J@sg-M0M3=c{JP z=lgm%E3V)Dw8d%H8jlpB-+{hrBzNRc4?6xmg}DL}+ovQi*;3-_QFBbb{LIWLTUN>z zDiZk(z*T3|7YuU#vh(9A|9z5!|ys^F8kT*_q6s@nw9SjvsM~dKG%W& z)sI?G$Aqx6DsB8h4u`PAE11h!{{F)hm0z2qA`X07GM%R?Zg1d(=*P5itx6w%0U@6B zZFUH?V{D>1shwDA}186Gg4Sx4ym25=eI3!1Ut z_w&!`2p2T*|2XrOqT?5{1w}&G4`C_d(Z>y8UmTh&p7i%$) zZnXRtj{&CoKglJGjPUFC6nOVJUOd{DI?p%FjT@lV?6M4w;;iT?ypZ2c?y@(T&0$(IQ>xEpf6awAoJeg zrq|ywm4A^(c+0RrgdKY+@(k9TFLO8QPAOw@D>}!gu|R!E*#rq}E?OROf!HuK1b@sI z4-lvZBjkUk$Tz(Gtg{~*%;^t*ANj|@%E z*WD}nc&YXmp%>?UXX{`V|1KZ@svpA)sJQ$E)`QvM!BzFV*qd|XtHpAHt`1B$C@lXh ziY@qm(E~CEN{@b652i;3i+5koGB&fhp*vg0{DQ&NpBCR2xmAOommPWB z@#xydy@I^w%^l5ZF|~h|JJL{m5(0mS_I{(to%4Vz>_Oy`*!*?J6HiXf*p@E4=Qh(= zaRnZCBMn8!`%aOkp_il@yiG&2qc7pbWabyu2ESh#)MGm1v~vu{M#%j^k*lf1IsJfn zfl~CzmRBir@7BxQvJ=}gu0O$YAxB2Y{RzuuPPb8c`=O2Pq?4qH-9eTqDbKg5{K*{G zpGJ{OUtu8R{(|MQMCDD@zjyG_B4N&3QM#c|@)~p5t?Du3@$LM7j3d8c`OGIkt@JN9 z*GRMk2)kZ5@ulYb!bIn$B9beyzPVnez@cV4DAIf4I@;@jOJ4D|F}S z>Czabgry?$20G=8;|k7Sbp3%8L^*en*KWy;P{hfsgmdiY=Y2Fl{_sa!rUWw-9Znl^APsp?s z8)JP*&KJY}HWEG&@|a$F2X;k5tD&$YZC-lQYPg z|Fi$>(KnB;R#ldJ#jCx)x#|rzd)$5;aTs!VpvDIWERR{>!k&|BQVb3ruZ?DPnLFV{ zp{1P1T}=KTp$yS39#|d=_x*jHQO~Z+wN28IaC1-0(N~ZY3K?VG{(IVj zKf6%=!3)b})}QEAAy{D$)F z2KU!TYs=nNCBMmNJ-{;wn>mhLhEaf!!w<{B8hPzK{Zk!kmmQL2UeXZO`Lku|P1!N> z4`lnoL7*nP(olo3Gm-Nm%LIzt_8Vm@etu2kU#fCsTj>R!KZP%g7u*^nu2AI;OGUI( z0G7wJ?PH7P%cvG^=<;d|T1k1thpYJ?5LuGaR#dT}8cYA$S^j!Y?(K2*C1<%&e#@?9u z6c%BMoYVgu_L)+)DZI}^J*mB_X=#kpBGHTJe!}HYX1KBCiBROV&HjB$u)pbAYsid4 zNe=urY2R-I3ZTD#Mv*smT%JggcdT|WFthH*#$DN`-MLk*KAI&;+s{JB1N5v5{-Iy zZvo%plxw0V{ikvh=PgI&hj}Ol4~rmXOoW_CupDf12X9wm*z6abDw^C~mL;jCM>a0l zK%BQk$f5f-f-g?tOP$@6>{0T|7@OMKfB4BC>x37gZ|u-^3L9<&f-gbg&#cQk>5}iC zTIx{UZ(X&*>(i!X$Hg!O)c8aPI)pq)iaa?U|AR3GE89N_81Accf46U!Qbz^a$M-%V?jd0-@7(%PV;LcAnWZ(;+TxD8e4CZM(CLr2 zY|3sPuijsy%cW$3N4{j~CLK%ebc$TblF7@je@P$QzxKw>3)d^#56Ve-gpZO32aTl2 z{kMLUrO0DF)GT&1^Tw2hD`i$|B35}i@_*Q~ca(i0SROeA4i7HIh9dlD21PE@_QhU@ z{I==X#r5plQWjn(q-`mqHp;v^id?v%2zhd_JS>e(to)(stp52?XG85a=45kLYqzKn z_etQm4&)JygN2~Sw>(A89@!;Rn}3u=mCY31=be)zq}*MwcgHB}77ySiql!YbXC^F% zDbW?2vzp1L_{pqBxcth~f}+^NrwL~aC={tk== z-bw)vjaA2fDqc&S41RXcRX83Y|Sf@d-H}4X7&yy^X$z=?Gc1t zO0ZshUBRLM>2g}$^m*&*)VY&ET2te(Twd|o!jHy_ea?+3}{cYP3$=Q6Epl@`i0Vd zcTxd_{Z(Q4tZ%!58v^nQcDN0?J)XLj4pW? zxxeYkO5qE!KYifBO3ZAmb`QIuH@M>#t{4Pfc=iQBHH7MZW42$NqDSZUt+y=Y{q~(I z#uAGy>$E2(+X}W(_QfdUf~E(>>|_)`#vu(@E}M!){`oE1Ep1yC_byraZYQgf@q78a> zM%1;u^Ul4p^I5;(eiMlY(_$&(8@$K>qC3O#hFoq?4&Q+P3%-){pcxwB-`cQzPFa&j zF`hrRp6fR@Klb=fQVaX1?342;^YiHPY0(5BM+cU}7G#&4>n|Fv5q-w;+QQO=NupgV z71}B5o3Z2!w+b<>9{y70@T@VTr%%+Bzw?l;EjyD9rmq&!*%?_Uo%A>`}9 z^0_OD*|-Nheg-673&|g^d&e0+`V{&6Ua3JW7d`jMr5dfr zG-Cg-Dt!qqq4@Eb^68TWqP+&NJYKowwiATQGnSkUyHaAADE=d?Y4x{AN}OhthbLD4 zsmTcWhOm52+iCGiB`mAAr{8&Wfc1seVe93y*iKTugBj%yClU~RBbd)3_Hu1wtpQbpmN%Y zN=hD&CWi>PwABH!9TqU3N!|6q{^*$jrUrk~D|cQxH59Vl&b@l{ej43q2!B{WkuyPG zMs589p=rsdg<8#-H;63>+L?Wtl8>UbgOD@aB?!JH%*PH}&-FXCphLLgNPo4ROpuAp ze|r{OBkmE7j1z)SHxPnr1#_7i4uwpS7jQeq?De|GSoyPBS&-168U36#?HnY*r4JAU zcOiv4WY5i^FaF3~`&7xisXVJxLi|~7ana9HGji#MKya;LE~{~%`Pw@*X{KqG=PExa zKIc|WlYirlnLzO(#vi{RgTDmf0}nq+50gzAULg>}12sNuU_IDBNK0N8_ulz&sO*i? z^nGUC>3{NC`zY&@vGt%C6wz*5SRSjUaO`2xgZ?a)tNmHS#Mdq^7u(m_N%@}m7kR_2 zMaZ>>pMGmkY+}zCR6E%~0nB=G4 zJjLaZbzM3{R$)Che~f;o>G4mC1$10=g!SO^I>=&q*U|lKtk(G}PLED@HA)w~)~1~2 z7}XwBJ;oVNPOw~FC4sNe>n*qQxjuas!M@(M@0kbl&J@ab-Dqc^7vkLxHGSPu4xHA`(W zfA!Meiq3j}%o}2bk5(RjPT6;Z+VhX=DOXqy+o8`pOr~aYr`><}$HI1VM%w2RA>Khs zJ`<8N+%aVML&ZNgSPqNTUHv->;Q=)p`W#v7&)s4!cP)JUfs)T;lr!8hi2h#=_zd?e zC8urZvajV66zpVKofj9pscLi5Ku;rm-KIgix<`HqkawyOT<{ss$dw4s-EgPAty5Jr zyK~BF?O##-^DI|q)7P7{du1{H0pKxq@WjN(Eij}%fzM_}K8Hqcm}b%X8#9((j_0jB zl)L3(WkJpf+C4s1+Wj)JxYU^Uv{+WaT<&ixo=p{baB8{xA<^yISIF;3`?TkdJ$*ja zrr*k4gfk2AoRydjC; zdBHrk>4}Cx7k{%Gs|WA*KhXKwqS1ZdssQ?Wf_7gGJhI`9?<|dRpNz#D=JH4LWmkE5 zy7BFO8oS6lEiNTCQ!5DTGM7GVcJz-7Fj71^qs!o@3P^TK`6rf%JC!CiO$EO{D&3uXmlZJAPAt zO3q+cgU@cwo2h$f-|^r*a2l7`&x9WeL-1F@eC~(W9;pws2HNOycrA8~;c~cJ9aOJO zi+5_Y`>yyyyTJkRzQpUhpf(gS!V&x+n9rOQZ+KBUSNYxnho>o)MOXc6gtXU))5f=& zrlNmT$SOsDP-G2MMI{3Vf*TBTSsztM+XeK$>w5Bi&7q?qtJ@o=T=+7V?uX#LcZm@x1E2m^?mUeIIp(1BzLdz0@`^*H67T$amS+>gZ@enavX(GR1DO6*=?{Y^jfL;8@eOp;9+!m;A;fZ1HwW*Sb?BvMp(@Q}o~Zs*R^_6wKw=HE`o0)9-z* z6Ynh+I#!nRUEQamr~hw#)s)83HyY+L8K%#6oE^XN@=Sva!@`BVraxm2zfu2NU-j|y zje)u9vT1iKk5zf)7ysmTFa0_wqo4Ddf6d?es*k5{EX>7fq_4~@ZCW1pvm|HsbpiSM zPgwP4roZ*o7*F3g3U|QDsmQHgv_oX!@=KT5@A25p@F?o|TVIXw^o@tPEK#M&?Gsym zZ(M3svumE%e75?)zy~aU>#I4QzH4Bv+6Rx|Hi2b}yK8xF-HuECS=~EPjsMl(`f84+ z?^>9PbxCO$njJd-Tj;QT^HF2|2|wf5HtPPZuhw|_u7kN;Tm|y83WQj8TwmmP&!OkQ z-ql8G0XP5FS8F_d*TY;US)oKxn=CF-YuT9>B{**nP2PC;t<>N8YLBPy2AHe-h2y&O zp~#;uzlILBKYnzsY)Y~o=K8n3+T-b)0CO?>kKg*(yMMv$h4w$(%<^( zjHmBL3U|QmsFCm9G>h`1U1F>r~r|)LKW%#~fmVLVI;uoJut@qzOexH;qCA%#BN9a@f zJOti7BIcox(2(I5_7QP?3q@a50fE9@7V_E#Q{NVu+quMDXug{fMb7u&okDWmsiFvW zs}a0p3eQH%oLg3}hJQwIUsyv8cVy;~ys2D&@xU%Lg142zTU@g7nvtuArQ9pu+xPrC zHKumuAJ_Yf2X>PYyc7ygwKw;jd{{DHZ1bE?8#+FC7h4}?drsl0DuUAi2;MddZ}DtX zZnuu&Q}(&)-g$TR0|$oEJ(~XFq1(Hi%JV$^BdX%Cr3vqCo{&o-;>J(DZMXf4hi-2w zg=bUyR{E7`liEj}YPH2#AH27GPUs`w@zEIWU$rsXyMw}8d_4GY^HJ>*rFIv~#*N}( zzmskzJ)!8QHip0Kr0{H<)azBB^OQVj+4Nt1wpx`zX%6$Fzj)~O?xOG(3!KfDZjZ3~ z6f;HK-%Lw*maf_H#u+|Z~HAKKF?2<|?Z%eqF*@6}#R z*STT_*SYF9zOyf}{P}&39v?MnT)(LJ_;J?h88DY+=6}wd#vQT(T*(Ri2i;%2+WMsI z$yzf1qpG4y;|Al;8~TNN(%-YbifMlZHBc6YDsmh54b2qfQA z!g<6hUv+qLMB6c&!VOJb#r{C+NN{W@$CCH6a|?cUD5S_yxFa7CXl@RLoANND&LwL0 zb;&IOg1cLSbDctFWvWoPBa3}B_Yj3!zmRho*Vz?E_PqO0_G5C_hG^}+N#zvo$f6w0 zJq)-E@qn3|Yha_$3z-A+x@Rv}I^I=lIAi)OvL2ZRDD*?~*9dPr?^HZQE%I|Y2ygVo2w1l>a@fTk2c+aGpJy&b? zq1F7f_5kiJS=25vC-s!hz+S4Wq_h0-VpTbR)yJRZ=owI5( z*Q`Wi`OU93zjC?YN#XvB9~`4_!w&z-R$PBqDY-{)_lK;@X_qUFL@!gg|KbP7Dcq>K z{{lpQBsb>^`Lz{jJ`V4!QEgJ7a7R9((EU~bxD5Wq+x4S(lD2d0uCEJH_#|7Z9%MNw zJlaQF=Yw~z80Q-@*Mf+Hg)o=7)bFR=GU;<4cL^vz4pDh+)%VmWPk=VxsM6;f-w>bS zLIViy37E_E-BI0AzNl2k=a|Q?{=-dIZdw_L1(4@aR8{Ho4V>Y_y%_1oCt*Gd+ku9q zyTp#fpT4sA&gnfapSD>U6xq`4L7#UR9~eXET?BL4yJa%pcQNI}eOkTqtMjG*A}tN& zTzAvCTElk6OG%BH7yl3Da!-GE=bq=sjANIo7JggT$=+@Ew6Xj--Ok#>T>RxPa;8M+ zTMTnqntmwF$iCiZ_dYb`!$;$ql~enC9xIdQid0o~Xk6dmut=gxRU}A}(n{IeyEDYd^ z>EzbiqMnYQ zL)9^mdR~UPEYIG3d8@GgwX@B>U~{1Wr|a_y9_X@>Tzuan*b}eQ=0`7%h@LO604~G# z4r}Q^ib76-(J7TjUfIXq_ErX3dr3v$b>Sm5@%;(B$T8mQNIh#{9_E+ICLiDPs`z} zd!Hr!#nV9Aua3e?HO~*d5uP}xddXSC^jl!Z?XaXgm%n(LNZxe{@89}E3(31d;r&~G zfZGO$@lsFW1$E`_6HXM{vT)-<+0)*|20yM_e>?D3dv%b!n-re+Y(F)r1hqT-FK*{L zYibIf%APQ*73@?VK1LAjy-ndwEr~jD zqC_fsrpO1bE6vAy1oV#W2_x!$6XPQ|VkJ2|P`o=Zk2$5oA>@YooK>ue{rkk(^I5f? zdMCc1^1$#w@$SMrY(V1rMy9ix{U^UZy}R%ZPt=y&9N*1U9vB`dUIT@`4_KQ zn@(yNnGOABwVEb%;}(?%h6jq*2=kaKp6$K6=I|2P4b6Hf(u%si{!ckY z75A6Rw#W!Qz2sixWl80M;ep~cQ+RU@4+@K2bZoZr{H*+>H0#jiwYHF@+$;#{-zhzV*?=gs-Y+Z3|r5H7XY`Vby;n z_*R)%=Mm#0Bq~VJOKW7;6{+V#n9Eft>cE#VWoO8_bQONi<%%JjUP#`*M!feAaRb14 z3Gxr2?<1Itxf;$55KBJqc!x>d&Ptq3c6U#b;WT2MGt3PO0wtPI_Iym?b)~*^6kTWc ze3Jj4`?_wb3g#E?D0q+Xh&&7tArSgK`M>ehQ2IUnzwy*jyjGaUSt0J@+-WNwIVrW~ z-V(jtwN;;_AF%)3UJVql4d!vxtdlLV`fjjA;{WUHOyHsH+CM(VuI!XT6eThCwWM8( zv=@>z7)!>C%m`5lEfS?&C{jvFl6I+VX^~d6c}i(P(uNlGKlgplj5)&@^ZxsM-nZv{ z=kt7j=eo{yuKVmqyHfo|`sM7M-L)C5y?NFs-br3B)7lwM5&lbh|@BhcML-Ah0Jh6W?>N2a-jAL>Ol}v(4zF%EoHT#-Y@ABEB zc&}j|ncBX1hW3(Bl?z3V(Z@%s&Oh5VPHkK-9>@@gcy5GwcF}o9*Y7m4Jf9b`_4l>d zn@voH?Uf{6KiCwE)Iz|%rC@Jngz$F!25|Z2(MC=4T^#qb&hU;zbJSCx#DO!*H;j%o zA@UCPt{DDa852L{Eqr|W7UoKQmL7Y(^5@I(Qq!F^H&op`KjOt-aNYrX2h18gkVs+C z1FDzizBt;utw@Q0WcD(^6qu@$n_#S`g*MZvo+s zh!EU&Fn8z!^TGkcA3Eszb*ngcWRJgEnXpSN1i#k`a4iY04>L404D6L-GQc>F1qL28 zM$cATgwA_dM{ebzr}slhmgLCcx5zv0y^u4n(+amF`c;---6tt#KR(n9Lx~gcPE@EihL~PIb4$nxwK1?A48W&nNE+c8~Xp8HcZr0WOFOCX3B^ z%o1bsN{#%Ew!(ZllUNJ;!}EV$-Yf09EB48r=bH4&7lrsaDZmHuf$_nkS3W`DMhXhQ z1fu+(V7}zl(uv|rJbhe8w6iR)_!%cFQCy=B;_r0EpZdh;2yQ#fRdTX1?)*4fR`G0@p^f?a zh$9b1ZxAye+B^2{8vdpy6o?4!7nm!vcu@1ppa8!a@o|BxPy7zPb1rs;gDjVey#oZE zT!S7AMR32u+yRVMFZ{7k4dK!DG$%o2<|tS ztNEkk;71E)xx4(`&CAWt*Z5u}P1CF(z9-iBdw-aJh4}S5%o}=8^+ElpQu3}+Ix9Wo zdN$*6;+07ch&%*3aD-p+AV8Gw2h5YbI^wb&$trw#SbVlaj+Ei7Z*!N#Pa*o5*5G{; z?8!^8f3zp8Q1#ydbH&GL*S9^n;!yBS_jr4R#DXT%WA)Dlar_C^dBFRnL}>BIi1K#A zT(Z$Y_9g@838ct7sblZlcieHh!YA2|$eY$+orj1kyw!2Y2yPe5RsNFLT0Ft7`<33R zmg1+`2Q1{YNToZ7c5H2K&EXQS>Vi48z#fkv_`d+3Z{CV*W+I{1QFdSIBva~Qe3s?D z%Jo5Ozu^1DtpVSZ2wCq?UY?-%()_4kTL8-6zXkYlw~cQ+D4OH=V%uXAlkodpOWckQ z--Dk!70xG;D~M=>9e)ILcCXzXU9Xtlv4lKX>PxfvGxIx$(X~d@N;1O=yG0zMd|z%(0Lg2-9-O<)PkUsGv8Df zZhkXT^?Ob^ejkEpI(=A3bnFoR{U@M5X_nEqTg#5HC#}_f;xhDl@pYqf-^uv-YvKC; zqpk!f6cnF^Dkqb4e`i(YPBhwiVpZe2$W>yQlZo$S-|-1v)DV&fCBy{y(M4y^E_>DZ zedVA6^<&QA!!9X2YB`0U_dwVM_`KM}g!=ZM1X(~Q!Q|ez_?ES?<`Qn2e~p|UPAr<) z^dEkoM1MLx9>h&U=!pyHG55Y9?;cdNvST>Py>Q}cMS28bAO5H1mY|1+-EG{tradZSM!>bMDPe}n?o6$O%88e1bBqEZQ zY0CLu{1K?@Sd8BjiT3CJq9+CGiK{gJO5e1|cb(Ja;~Dz%G$qNh`Q110b4Rcqu~ZA* znGc}{`UJ9(@gXh1*JvFYYJMUj>_6j{NBYW_A8*pq8()o|zZA;v2}ML)$O!0+UeuAG zuJeUNqnaIJMjw5%V8W(R<@mkOBItPavJPpVtbne3i1L3oHcULxkU-m6wXmdWvH#<{ zRroy$!gYBeiLg&jKu7!Psrg;2e9mdvZatxuTT=gbmebHLeS8N5Ea8Qu!2gf5Z-9XA z2u;1U4Gi&1S9F{Y+&`BWmT#^S<=RL47U1{xD8kNx0(zS7mzQ=LP1UV2D8A&L7dJm? z*K74NynaE?cblW-K-;nE*hB?UN=V$w@FO1(4 zi$d~;f`E>$+u)M=uI+b~FFkn|{c4lNOv{5U#`wMH{pfH#$F&UMH$?$G*wW09wTIXipS`MO1aJ+Dc~Dh=uaPU|QoB@PNqX7)drKQ_;#51X9c%3)4+`*m zVh>@5nt;w|))?EjkKEez_HGUzF#3k9q}G&}kUsKYpE_Ow|F4j^P#4gZZ*A9oYM8u6 zT*LC?ekP?QB3$K8LLc=cpuo*f$b2+NKu7y@_(CP+iH{w}xSNzkZF#bG;bV`6KJp+! zr?-U&zYZ4A9Wnn`lYiA}ugdS!>)LkZMl#5we2(@J_X4^i>uCt+Y3_b^%KVMk@u&rZ z6h~}6H*tPr)EtdI@*qc#n=O!j93sG1leIA!d3wo|$#>6v+9Q2COWc3ur;a}IARoUc z7KOycPyrp?#^#br6P3cW#*5VhlYkh_qg19 zvE#of*Dc9!9*>M>I`@$W`SnD}gIWSQ>W`mHubOVz{_AAbvMo0+{8CM@e4B%g2PhBn zX(Q`PTR_jiH@J9nh|H&ao2mCc@9g&Yl&Wwc9^Jn1JSd9JFaaG6+P&b^MjxHJ@CK#V znq%FbySbMhAm*o_{xvw_&t&)o`lI@_IxttPBi5}-CLm+WxP6s|^CYPQ&6J6gTBWP|829sZxB`gy z8ZN+hQ*K$x_)1xDVfV{5a*l=%|IK$zDeYtYA;|aYXZ$t-)*(-6H_9_D%6b(|9sN5Y z?fdY!2km>8lcr+p0)5s4*HM^`7w{bR9}53Jvjf0`trWjNIx&ny)teq%4$^@MnvMDw zi-NKei@dsv)jiW5so2u{myTA9HcV1FSaZI6R$12BGm8=~T1(|P z;P>D3V=ter_|7Sw@d5@T>UShu4zbVjhb`A@?kN;g*FSrI`r#CIRK2()e($E}<$#NT zB}?!Jp+5@NC)@uCnbh`Y_`FGLis#K4`LkYk=2<0^KE{7MfA^l@5vpe(pl9)Mb_3hP zE&uF_#^+K~{YEM3dDm?2qhHcnj}y=cyGFx0;+kTIDA_vLjMsVXVZS@=a#Dh@ zI1(68p!{bjprb!_F@5>o=^G{J<&Np9)*2e$Z7#YBYoAc36kSha1avf?4-fve#3W&6 zY6b1F%apwHl`of=2(yQijCmbLgx`z=boBGzEz@<;R0}wj|M9VQ#q}d=#(pH;kwL^G zl=RW|7z^mA?oqrS@jUuYW5h@ehXTEo@^`1H9^lR&bLUHWm;9i)6qqaaMZtZ~z^LUK z3pCxc){Z^(VyyWrX1d@!6v6G4^6@}M_{jv;Ax-dmaN=b?{j5vemHY2U|Ne03p|?S= zaU(~EoAQP6O$GQ1ts7omHO?vBwr1XcrQ615rbS*jpCdRvfcZqqN0cAz2!rOE9Ah_a z)W1DZLQ~&SZH4dmY1#p)-^%fOY5D58H=mbkkf|P_4|Z7c>y!3ax4OnwZTx)kLCzne!@@?cdc5*8Iqr*ZUNd>A<_AQa2&QmhaRRoy zK{`5jq(}WrT9^AlY-ZFF=KyJ?(!(s`ouuAz0;O<-9d-izK~uAC#~ZGQv}WX$b=%&b zW|%!wUlw79VCoiT2iO`1+Yve^sovGqigzLvRA^*v4`{wQug6vgiuDm=R&0qewdTqU!Xv5TQFkBwh3VbVb&i2?G51+!<~qE|g~dXdQbnj{i`1kxVB?}Z{8R~ zNLh+vw*JXdb35+(?s}_JIRU8 zPc97m*cK5Z+K&H|BibOim^yDN*RpM@gvd*wd?e*gJzkw`kd@?W1i+$951^50^SbbHm4 zpl(kLBK){SB%NOQ524eCg$P|&k#u{-y@0OhdTt`=^~!%7Jzlmz_Rri!;`hpbe0<^g zZ>dQ9Uiq&VpX)K6MTq+G5J|6B{^Qr{i9I3tZ<$Css5pS~AD=e*JLM^o9;#j8`A;OB z<**L9MD^_{6DP9z44LfszlQPq3UyCrti;dhL;V--G&VBMyhP&mDnFOodkzy(|0`e} zG3SuICMRwz=#+BY>v^m)XK2Y=rB}th$MHNJ-W(=^PZf#ZYkVQd=Pg^IzIR~v4gY)x zq8_mKJNm4L|9RO8RSqAx90-5*8rSz_FTQMr((#3L5ccxc>A2(FUOIepib9vIP~`xt zcp~^6dsj@j-}%bXdu~%mUh#wV#rvLz>NU>e`JvYwCnAphVI9Q$La%;EZyjPZj%@b< zunuB8iH;wEfdf(}P$V66yM#N3=sdhqBptLpoFv@Sl|s}{kVracd*Eb`wucVuAljWO z6z{||Hx|U;52F0RB5~pQCC>D@;0^_$It*9`k)PoH5l4rY>V??>R$=(_0fG;Y2Vg!Y z)g$~A0`n36ckILe|6__r=&yqH5%$6TrQY@tsa=>KLt!0+ofCxF$(K@vq;`azERpo# z@sNO>eENN)bc8P0(aj$}hJJU!D%0$`N*~hWUs*-e;Wj zO5wun2p37G&-MnVaD*KZBJumQqgUz{W=Et*I(^y!r*4EDYXG0`9JQf(ySUtag;~B4 zf6m_hGtbdA=t+$DRnB=r@O}>GoMIR&fO}94!Ht5s8lRhIHazdzw0d{F!;^Vc!%e?P z+YHg=o>Q}>ga9IV(4^=27}WP58s>`qsVt`b%3RrS@{_cs)ZBuD7stGGTh3k20Pj}N zz)N)CR74;fe^erjdpjKRd$Shi%Z*HpqFgj6{}!fFdG28s_4RPc#%v04KZZ59&n_g4 z&bc8R;fHlFPjd25^$pQax<1%#Fwi@t_Po_SOD13-r@WSCl&}ywlS-UOrtm$c10n>$ zUk~%8OuKGGKc!dEkAHn``a{jXQ>E4`VF_oSh?N;7fXxoUoz1O26gLLuN^Ectb2M6c zw9>sw%ODSbVpuBlvJ|*$e{(4DkE2TL^d$HyE1Ke)r)6aWT5*uNzn3qMe z%K^(2vHQ-2CR2wdEbb_M{dEN~4!7m`1$#(`$q;tmsKh3iE35Q*mBEd(yT{IS+Icd@ zpw(iKOtxk^u`ksYoWsTL4Wm%$_&ZL*&i6`ehPiUCKb9DrpA=vc`ef^fSfBKx31_z- zn~L9;2g1&b9_ZaqoNa-5vNnaPTFnctXyt~={Ek0(IROtl-@BSZ4+Ll6 z{X?mK_}zYpcu0V`x~I2$zS`GXC!X)c+;Z>^%PTbe8?^+F2f(%H;d%$Mcvpc6%llhl z{)hpdCkA}DyD{kBe0#}aTHMAt1C}<8#qR?Je9Im_n;F8SFhl!$r${0Z=1ctc%==RD zW>M+3id|O~j=wE<(*7japUb!E;e!io0x0-HOB}}|{Id<_OJxl3jWy~R-q3Vw(cINy z4&sw1ndHCY`rW#R?*pzX7v}dQn5$@?vU-L~VdsVsomz^@TMmlZnf$A{#PvINUY}E+ z!~AXq#p6~e&n{Sptdx~8vte|(>w}}AzNClCbXe+AnO^w4 zG~jc=Ex)fHuUb)l-wksmmsx72Nk49TE^WK^on*_!)hUysqKfeE5a60~{NfWD$YN1^ zLYb_7`zL!~zRuk@o%tJdPmFOY^n4U2x$n-Vv0lq{@q0)B--5&M|Gff45m+L7?FjMalRks%Pxtb$V;`nY|S|TCQJ5R!r6UuhbeyH z#5uSZH9OM|%* z^qseL6t~?jVT!3A%zk^!CeChMc{VpbY&qQi-<^<1hq;oQyS}~oXH5FfX}hcVkyj_p zf0#^u6^!4*0_xF@!wn0eu$dHJD)Ge!PDcAV1LjL*d0aa(C+XmuwksMf(jP5js>tl2 z#QR5pZ_o8}m^T}on!sxj;pc-e*U(gd?lbSB)mC*8HPQ)t2Q6yOUf-0!DL3eU6Q5^r z4Nj;J4ZHTOCqNMVLoi=re+=tY+2_KOVsDQR3pFisEzVQ@@t6~TmfZHvi^3i}sy;IX z_`jMfT2AU`4xW2hYGl@a>rLt`wKbzS-w8`@d#6*eY|G#3k?ki7=8Jy}d%A-1_OV{# zq}V-FgGG`fJCz?z;KZRNw|)AA(s`K>;g`cOS7POfQk9Q3mfDw&?(NPj&%JJEqGG!b z?|0gmfpH1%ULf8jxCm}G%$3=mvS-2JKZ~c{FdLQl`L@Lv#>$utWkh}j@A2T@HSkGe z(ZJn0*oUp({CWiD57ah2A6iy4_Tb;ym#P-gI?hhe3!O8A_%4`Z?~~G4AbET9vLMpW zM`5nQ#A3G9?m_i#akHm%RL*#oyDah0*foSdu=|z$g1Of`fg44I#di+O9iEk4wOcDR zIJ`S`tizwW?zbOLOnJs8+BDwD#FFbxgZFJYNv@g<^pFP9<33%WhoIq^s!MROFm_JXz z+%Wm#=*Bx&R+%P{rv)Tsj{8V;%6j?&mh< ze-{rNd^+p>KBdVGPw;rcc%~s?-ry>De-r-@u&{#vLD+u^=91mEe^RyoB*&h!#OM3b zcjr&0<&WQ}-@^^1g8S)$y;w9FSRfO|&4;-XPwLI951ws%r~2sn>tjm0^2M52)8F)P z!C*$T@}7pdlBB$oAH6SLi!TY-$~1V}a=$HP!B?doE;}@g#rE~?-wy?V%Qt@|vzwBV z5%^hILgu3GP3p$li_d(TH5~E#rGS5}DKy_eHu2yp!mnpwp4`j&V>kCNa2@@!>W^3M z)i~?=2kpm{@cX5}FdVx-j?J0sK=2A-p5ztz{p@RXGNuQ=&~<_)bRD0Om62de@Q8c` zh{EFYEX*^GF#O@4ID6NOrH^<1+O*X3=vIg89?@JLHty_)R|NCOn$@i>Zab@h3nYrG0d;_P;9*B)nbKAR{XmN)T9En)R?5$37xy>q>6cH@jl_s?U)IzDn)VC z#pWd@KR)?tE&5%5AQ_+cv;{m*v%~NC z0E+@|B2V>j8En8Muqf^|0q%s?^-E3%C%XTAs4%X@Kv#-tbmYTPe14BtUi_JFUkcL? ztmPy9dL8EKC@8$l_>Xl#(qXv!vezfIAGqFJ3%=9JV7>xyv39{79V7gD1Lh6-`bBw^ z{rUaBZrc@B{b4&!rl`G8@8I&lJZgWuQkbXx%rJ^WTkLAG)JozVwP!Vx>U(%B zHkD0G(j&@O2JS)ECEBw! zxYv^Kdr!0G_U4drcN6C8w=Qp?8yUzviOu;yfB1@FM7nU{YzmP-!2}jDUK5r-t6-kO z@Wm&BybK17^e|d_Y_8VHjM1L&YL6574OwR&@P?rb@KX^>*bx@%EmnOKZ=-kU-K||)VVx2Pa;p-aOVlxOeXKy zT~t232XkdL&C4cs+Z}uuka%g9;mK~X8&r@pG~ts^!}X1pNe1K@1g>pW&gZ~Fi%SD@{p4%>wE@^y^{UKc&2lbw5d+RgP z4C{;#*@uld(0DOo>_+HAy8g04(62CmqrVrga-Qt z_Ba1mC%{iU)E)2kIm7Ljr^DoK>UZy*LyGFCD;e0@ z=xSQe1R~+%NteBIC>QQJ?ET}LqWNUoT+_XgIo0?+S)gad(<3eq=1j^6i|eN_ zU*fQjp1A)Nbw9f~ey`8Htj$Rt`f=NKe4PsLt$BQ+UjoeW<4#l|%Kr@J%Po#eVC>x- zyzRFBOnP)-1Z#|0^7x(jeH(yp(-$9GOZN7raVrL)^BmTZx>{`eJi}XBGgsfofTr*7 z?pyXzeHVT|1*GH4F@&Q5+C^v}acMR}=LM{zyzl9`g{Kyu=_Jo;4>&MQE}c$$QDBAl zUx1D+&z|5wu;9s`wov)-CCncX)6uCdIjt)2*lYhG3-$LIFgS4pz7yPb5e8=VSgV6UKdGN~_X_a& z=3$1A??$HUC4C%fGp8Ppu5%)8nNU zq-7d=tHbk3({G-)@>!P`>+_3iWTCSC<641@BJ) zZciQx_O+&i-UQzgC&E8XFkfO-((!w4Et-bI%DcNf#x>9HtZ%8sqn50Xur~50~#3#t6c4DFb}Ig?<-40v*2ilzcdOvT5Y7 zBl;41UR}B~pLJ!8!TwY?&i4kp9~&$48y0>(XK5kN z_=t}?LHT>~TYvB7NwmORi6d(Q(#}+CXPnR(eEr3V(?@EPuSWdhe80^?PZkx+gw5OfhAT3$&5x<`Ul)ER-5Pl(cB2s#sj@J1E>zEkd zrKYIw(z-BfW7euuAJuOgPxnbr4y)MX!kT|CzWyh_ttw_ z|6;O3Z11SIa^lp&+?1azG)}+5<(Cdr-r>5EmL!uC^E&*(jGBsf=_kfN$Jbebzk2c! zHH-~jj>HO$@XI%tOP+IMOT@1SD`~X9@$_46lh!x0MrYh384>l+lZU{pR#+h27rg5N zwiSS!im{!E;o&=q*?V*9agi->*^ww{FP_^f7u;{xEF&7Mgc zbi3$)`ODmNuG5$5os(ZwX1h4|&)b(zKaER|B=QEnu0#cUrUHX8Iwyh<qm_%NxC!Db@j8t~uCI2)IP4g>$=Lu1&FXO4s9Z&r{N#f#hxJAt%z$ z>^W#ee9y4?SB^hGcM81tjPV9XpM z-(mBwFkgiA(qAxt>XiJD*b?m>jyf)`ZdCdCe(I*9r{5*|q1gN=;D^(^v1uEIFBRW= z$zW3O4WrzN1cV*GVIA4S^5L6~-!yx$K5MrXQ~U6~B)yTrxkP^go3{Zm1Jx=r?*G7C z3Fm!MZLX|9qxwI;Z;35$_ud^XM>&emo7#hg7z#*kbj%1&rK02TFU(b%?V_i$XzPa6 zh8ia&FJ+2N+Eb8x)B;~O#JHwx8jEcbLPzud0UlreG7V9_Zs4jpwcN0%qx<%jYd_0u z28K<;*AV~-Yz6FRf1xB$;^z(0iqgFB2E5Ne{O`FOAB6wt&C#HIG#NDfoAKS-{v(rCN_4)b7AkV|qxc@jj zZ(+Rs6_GGcu5D__!)p<*e;3G;-fsS`ePRl$X2xrhB(^T<1AK#sm;DuMV2)Vbnt=t< z@=lkx&8$;0d?78Nu|=}ko8W+>UuaGg%pu2H`G+h|Q&7v)lVEAAi2N|t&1A|nj1y`i zgp(>M0dq)${M}ox?eEH!-&>TjO?5!t%gtL31%h?~o~WjSffCCUyY)2CpTP{J;l)FI ze{^d)#-hR1cG2GM$M0Y&&PRZ?$C!SY+uVG4$)54SF7m!< z4xNizKpx_i7k}c4LOeK*zix<4hMN%YxFPE;RZ<4l5nspn@-Ra-OU{GtP;&j`j3MUL z@*Dqvd2m4;&UhHs5TYXs>yR=>_{9#&zE_bcALCN-F;^l>@qV`~7>5Yz5D#H-RyXiR z=?J%>>RArfAscEueAQ_Xa6|mbuDiw^4=)@k-g-a`^uPMl0dp=iuqTbrYsh*Z0PB#h zF4Q|0wBgdr@#_y=4BoO+U$gbf@^vVC!a|6>SR&Xn5Y`b(KJ#UB>Bp0Kxs!d;Ztuu( z@R%!^;NM3aU<)qz1u64isP=)j1`aQjPPbs4+*3(p~40_SLi4tI%@-)*QkP=s|P%4`-)o|#dj z@MhVlyM-y?`n65&C4u0+*j{naYoSLpyOdzP0kd~iO1jUz?iVMMxN9zL%tSYh8t)HZ z?bc}J?Q7Lq^&oY;%n0`Ds2`*OI1g`R#McjD|Ne?~Fi%=?{1&fo>)!vF(s13g-oQU2 z;9k@GAn=YmRz6=EolWI@PW$%f{)+W5kNkYw=XW)qHdp-}sQh5m_ z0p`jq9TWN9@Q7H_b4u<%p9#+2&X9j?--3O&0GHUf$32X~aVVm^u`rixlUZEQsI7}DZ@5WJ0W z`6T2Pzi-?z=iJX_pYlr=$iLK9PyXnz2E;eGRUB-J=Nb!$2;L@`M+(0C_Ci$cG!v7G zg!=0*6gDierqo6W@bD5Mc$;CKeDieGpM#byn&>(IgO=lt_*J@XJJxxUq_K4eqTwMp zTVT%IQpS0Y&13&$bWdBi&)KIwZr+uoTdJTQKtQwb(PuyHE&=ArG~GX^yYS!7Vkav* z|H&5Dey`B~cIFR1k5dgOziowiCeByRjQZnLQ@C}&E#vF)#_~6H2Zw26JaD-?z8cX_ z`LJ_2e0fHFF}X3njuk3B!b@{+TOB*=f@f|0Qw}ejN)PHU?zX`^se6-^<(#|@Y)g4i z>^uI~``k-qCbmQPdHvX%1oJfL;_qW~Zx_p{eDcV@{@MN3p&`$!rjVrYZP~b~hR+*wb$maZQ)rxF=T`VQT0ay5+Ja3?&s_9t zZ6#H7OkMfSFUN!=gEet5GX+oxyHa6}#M-TD2bE>!w3W}(^f2}u(KL!VW5IhK2Uv#S z9Dq5B?Yowkino2$9JSk%l6Li0fX4R*nmg5|Kyx)bb3-a-FBG;_{VwOE~@;t_h0HviByXm_n zu4#}Y!g)OpM7dtU9J8TY4$kX3R5~-hlk{M2h~)~!-0qIaqygc)bK*qpA^nA||M8Ve z{7}TH`)Re&p;39~wH`DdV-?@8Y+BCG!-EPP=Z!E=oko4JY*EON@SuI#RT-p1-ieoQ zSap+>Bd~7>XwP6a+K>2hBJ9QHG)IwL10v|6>Nc&Ee90c>EB^_lm%BR(;Dy|&ZSZN?P zPO7#|`H%n)-Of{`uz6BG|Ea}3dRjHDa*@Meb#TPB!`-7B#0->ZDGbelK}#P=(Vs_f@eZMGh~RiXB$O zYZ$@Pgn4F*W6!!eu6edC_{-SRSrN1WF{e|SS-konmSJ%RB6wObZ%oyPHRqyfMoZ4* z#5ZOh4cK_&LE*Yip1quHlgPZ8Dya?gBz(3PtX#;Rf9IR!(xR&;3x4kW$ev=?i--2# zFqkJbcBFECSN(x=Moq0NmpkbNIR{lsH}mXeM`9~_LgG(S2j&eMUANC=s3UcZp2xtg z|24>5jxmWaTFT?~*S}Ad)P;Fc?N1tFcfML4ZBu@Ji|dV={fpl`?35GW;pJPjVA?bi zN$MZT+{)5c*W6MrFzNoq`=zPvF^dmq{k!>>ygTxU44Y(QWv6RyD>w1X{i+3bPOHzl z*74!T$7!}VG^Wj9TY@cJBx_wWIrYo4&99GE+;ekT!}gaMCaiN~2F=DM#Vtu-?Hv5> zf2>_owc9h`!|lgA0>2hc{2uJL1N?4bMluJ#rjFgLxjydj)#`_%heb~w9ynJw`90eT z?CK(!+ku}I?4A_<*#1v`mUB^ktbI^p=X4iO>}PY5xdr&yqbX<4>eJ@o8;2OS98b@9 z_=s+#gZ*qzvIW1!&a^yvQ|0O2L?4Zjo4YoyoVfn;Q1Gj{8Oa9xDzBN|{#MiKpuf{@ z)|bR*$yQrs*0aI$;$|dEtk~UZPU%PAU7uv`x^CwOiO*E(J1i zWOn5Ba5t+_DbZraVISDoaUYTu_;scGTDf=ra?ZPd6+VwUsJFKJe&JJWvo?T(Uk6QS zIlSv{LPtvCmDUWs`H|%}6Vt$wF(?@3lGfT)%cLwlpQ>(pefQG6AD36Lk0@Zj0-M3F zCTrCmg=#fOluxFce5D$B+$bysYX-p8_^+4$liu^q<@4H;o07rabLD>w@>pMNF`D>y znPtl1ijoR)SwI z;!DEZ9l4^@*ZXPh_T%5J9z$FEjqS)6s&f~jRo_?U?!4eftATd<;<}q4f9((S_Zu;yX5wM zZeqoFVj&H$4@YnX}o{jlGk4S>e#cXiCk7RAHYi=X=iFWVR@*>vB z+?xCd9impgUJs{@09!RB%|KFau&=-TIKFAW|jfBfN=){9|LV4Dw{8i9MPBfoo| z+obKo_GmmED`u8hUbe1c0NCH?!@><){d&))@Cj3<&5ZC_O0HaQ(N zeRp8gtGI)TUF+wk9CBF|`ye0Nu+73fHe7PWE~W2_@+wADv82Cm-Cnx%Zw9bxbs#Mq zuZh3o$XlG>pSWCjZsA|2i_0}P=!xgyle07yUa~6#8tRfJoj#nUI!q&0_ehN5gUOr1 znV~^oDcx3cfjrFG{q{greb%S;q(jxG=PyoQ<`*h;PmG;+ujN4Z0u&;fn zQ*!!+*##k1#JV9~#OO}}Du2eCum9n(glx0eyQAsS^?mq`R$d|Ry$+1p?g9tNi1W~ew`eD`?M(d5gY91^V+N4FJAsWf4gOQ&IXmEa`}4@h{W z)Z}(;aDKIawyOL!&y}NdunG)hgfYWdIE7sD;prY`iQ=fbXG_=qu-^YbqjojE#>^=# zmJ4Vxrdm!?GhTK!J9WkUSCj8SzHkp)iOMar!=O!)EU~6xPcuF8h;Ly3{^g)}^>@+g$qbs5=;IM5JI10 literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.lsfiles b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.lsfiles new file mode 100644 index 000000000..27fe5c234 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.lsfiles @@ -0,0 +1,1437 @@ +100644 6b9c715d21d5486e59083fb6071566aa6ecd4d42 0 .gitattributes +100644 a213e8e25bb2442326e86cbfb9ef56319f482869 0 .gitignore +100644 373476bdc03f718b4c01471dd9996ee4497f43a8 0 .mailmap +100644 9651afc89d5e789abd9cedaa4f3b92dde7dd1412 0 .project +100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0 COPYING +100644 ddb030137d54ef3fb0ee01d973ec5cee4bb2b2b3 0 Documentation/.gitattributes +100644 d8edd904065fbc4bd06365ce378f57d4cd8f9f0d 0 Documentation/.gitignore +100644 f628c1f3b7b706f9d585b96041e5a4b12bc0f62c 0 Documentation/CodingGuidelines +100644 62269e39c4edf95b2cf2e6a60bff2a33b239b07e 0 Documentation/Makefile +100644 fea3f9935b7794ce86f04d22c9d68fb9d537167d 0 Documentation/RelNotes-1.5.0.1.txt +100644 b061e50ff05b5e13211bb315e240974e898de32c 0 Documentation/RelNotes-1.5.0.2.txt +100644 cd500f96bfd73e288577b05cfdfe337d7f2b85bb 0 Documentation/RelNotes-1.5.0.3.txt +100644 feefa5dfd4eee5f493fcf3091ec0de116d414fc1 0 Documentation/RelNotes-1.5.0.4.txt +100644 eeec3d73d01a0e9f61df666bad8a48a4f423438a 0 Documentation/RelNotes-1.5.0.5.txt +100644 c02015ad5fdb1953ad1a8cf5b0edd7ef7c04fbac 0 Documentation/RelNotes-1.5.0.6.txt +100644 670ad32b85678c938709b47c37d6caacbb5ebe29 0 Documentation/RelNotes-1.5.0.7.txt +100644 daf4bdb0d7bb24319810fe0e73aa317663448c93 0 Documentation/RelNotes-1.5.0.txt +100644 91471213bdec8d95209db256f594b0e1b3f3ec2a 0 Documentation/RelNotes-1.5.1.1.txt +100644 d88456306c503d9e604ffbb699fb7cadacb2e733 0 Documentation/RelNotes-1.5.1.2.txt +100644 876408b65a0e0841567a2c9b97c6f6e62cb86b81 0 Documentation/RelNotes-1.5.1.3.txt +100644 df2f66ccb5d2a6a038d1fcf9306e7ed7aae05f39 0 Documentation/RelNotes-1.5.1.4.txt +100644 b0ab8eb371ccb05346781e63c5ec4b71dab8be34 0 Documentation/RelNotes-1.5.1.5.txt +100644 55f3ac13e3c242acc4bb1b873272d7d8ff4ef84d 0 Documentation/RelNotes-1.5.1.6.txt +100644 daed3672709f6b94478fc6ad425897abe3433c9a 0 Documentation/RelNotes-1.5.1.txt +100644 ebf20e22a74284f7085a5027f588ba295fb767d0 0 Documentation/RelNotes-1.5.2.1.txt +100644 f6393f8a94f454f4f7b5cc5bb42881b6662cadff 0 Documentation/RelNotes-1.5.2.2.txt +100644 addb22955b4e4e5344f7f139076a798cb50024ff 0 Documentation/RelNotes-1.5.2.3.txt +100644 75cff475f6546402dc3486c43e0b6e7557ae621e 0 Documentation/RelNotes-1.5.2.4.txt +100644 e8281c72a0b997e90cba2c9a2c4153f8607a7b76 0 Documentation/RelNotes-1.5.2.5.txt +100644 6195715dc78a26ce9ac452bd64852455fb79137c 0 Documentation/RelNotes-1.5.2.txt +100644 7ff546c743b3ac2e75610473e496d93e44a6dbe9 0 Documentation/RelNotes-1.5.3.1.txt +100644 4bbde3cab4dc6dc0815aeae5a0a79f88ab784b23 0 Documentation/RelNotes-1.5.3.2.txt +100644 d2138469511d3dc111d81b9354f100ed344d9524 0 Documentation/RelNotes-1.5.3.3.txt +100644 b04b3a45a5629cf852c970ac995bfb80bde846d2 0 Documentation/RelNotes-1.5.3.4.txt +100644 7ff1d5d0d100fc632208f4cbb67189ecbf58e9a4 0 Documentation/RelNotes-1.5.3.5.txt +100644 069a2b2cf9e9f28a2f9e6dc0817723a38b25f475 0 Documentation/RelNotes-1.5.3.6.txt +100644 2f690616c8327f8c86477998358ee0dd17ee467f 0 Documentation/RelNotes-1.5.3.7.txt +100644 0e3ff58a46f3cc2b987f9c3216e4844bab2317a5 0 Documentation/RelNotes-1.5.3.8.txt +100644 d03894b92645f2dd0d7cb464d3ab1905d8cb62ed 0 Documentation/RelNotes-1.5.3.txt +100644 d4e44b8b09d7176c137d826d5de305bd6822e862 0 Documentation/RelNotes-1.5.4.1.txt +100644 21d0df59fbb08ab148e275e21f0ed182c5aab1a1 0 Documentation/RelNotes-1.5.4.2.txt +100644 b0fc67fb2ade1c7e318d309ee4d55d37964021f3 0 Documentation/RelNotes-1.5.4.3.txt +100644 89fa6d03bc038d6210e94d7fe9fbbffdbc84d883 0 Documentation/RelNotes-1.5.4.4.txt +100644 02823413987d5a10364d936327e3a1cfb38cbac2 0 Documentation/RelNotes-1.5.4.5.txt +100644 3e3c3e55a31fd7f6d51e1aa5a1d57532484dacaf 0 Documentation/RelNotes-1.5.4.6.txt +100644 f1323b61746ee5d7f2a9d2fc3835c2cd75e76434 0 Documentation/RelNotes-1.5.4.txt +100644 7de419708f77fff6f61e749b917b5fcecbe9a292 0 Documentation/RelNotes-1.5.5.1.txt +100644 391a7b02eaf32d930e7c9c274dfd2a2edf081f75 0 Documentation/RelNotes-1.5.5.2.txt +100644 f22f98b734f82101d47a6aa2453d5374d5dd8175 0 Documentation/RelNotes-1.5.5.3.txt +100644 2d0279ecce622aab935e8e25914a5bd132fa95f3 0 Documentation/RelNotes-1.5.5.4.txt +100644 30fa3615c77d6198a5a08ebd8e8c63f4cd21b295 0 Documentation/RelNotes-1.5.5.5.txt +100644 29322124881bf65c3ee6f5d613251b09f4a98d9a 0 Documentation/RelNotes-1.5.5.txt +100644 4864b16445f3ca1854cee608f7c15f5a6e0933d8 0 Documentation/RelNotes-1.5.6.1.txt +100644 5902a85a78610ec38f4cf160ccd3c01267b4f9a1 0 Documentation/RelNotes-1.5.6.2.txt +100644 942611299d59abd4bdd820e1258662067a304d62 0 Documentation/RelNotes-1.5.6.3.txt +100644 d8968f1ecbd930463858870ee872efd8cb672bab 0 Documentation/RelNotes-1.5.6.4.txt +100644 e143d8d61be1bb2fac024b5d5f270b33f4f898d4 0 Documentation/RelNotes-1.5.6.txt +100644 2542cf53d2e40b06afe918fcf1cd35de19126d9e 0 Documentation/RelNotes-1.6.0.txt +100644 841bead9db18a025638570c10cac72bcf4791f68 0 Documentation/SubmittingPatches +100644 40d43b78ee9d6c3827bcf631c1f41f54d0e3dfbc 0 Documentation/asciidoc.conf +100644 5428111d732cb38dbb257ddfa860ebd04088b4e9 0 Documentation/blame-options.txt +100755 ba4205e0302a267a5da6bef504f3e69eb0c4aa6d 0 Documentation/build-docdep.perl +100644 6a361a21367bfed4ae325049556f2e82e7e1dbe4 0 Documentation/callouts.xsl +100755 dbc133cd3c1f19dd507014477e68b8ada78eab5e 0 Documentation/cat-texi.perl +100755 04f99778d81def42e9e129b2f5b2b0551a0c760f 0 Documentation/cmd-list.perl +100644 61c376057c0f2b9510bf6f1b2beb42f9859b7f46 0 Documentation/config.txt +100644 400cbb3b1c120b93278472678ee7bdb87a74f95b 0 Documentation/diff-format.txt +100644 517e1eba3c56907ebcb1d478dceb184e53fceda4 0 Documentation/diff-generate-patch.txt +100644 cba90fd27c6a1baaca884328e96adc8a6da8fc36 0 Documentation/diff-options.txt +100644 b878b385c6967f4c64ba30bdfe8f9bd24bef91e3 0 Documentation/docbook-xsl.css +100644 9a6912c641edf52083b8996fdce3a0be2f4dba45 0 Documentation/docbook.xsl +100644 e598cdda45cf0b953a106d6786765b3316e2cc16 0 Documentation/everyday.txt +100644 d313795fdbc420e3395adc42aebe82fabda037d4 0 Documentation/fetch-options.txt +100755 ff7d78f620a35fb66c47e50c4eceecff00b643b3 0 Documentation/fix-texi.perl +100644 2b6d6c86547b2cec370d34b14ddef25264404892 0 Documentation/git-add.txt +100644 c45c53ec2404725394563a9fba40f31cd314adb2 0 Documentation/git-am.txt +100644 8b6b56a54409dd586047a1a6cdf1138e8bb0e77b 0 Documentation/git-annotate.txt +100644 feb51f124ac8a806e65d41f6274c58de64d2991f 0 Documentation/git-apply.txt +100644 c7a6e3ec050b7ceeec79d468b5ffa123314c8f5d 0 Documentation/git-archimport.txt +100644 41cbf9c0819872a322321455b8a5cb805efcc26b 0 Documentation/git-archive.txt +100644 c7981efcd9b86287bbea9ddcaf187a9bd48c77eb 0 Documentation/git-bisect.txt +100644 fba374d652723161c3683d1be98c08ba573057cc 0 Documentation/git-blame.txt +100644 6103d62fe3dca23c78b16dbdbb5ba231a6b39bf7 0 Documentation/git-branch.txt +100644 1b66ab743c64d980a43a028d57ca2f6505d97845 0 Documentation/git-bundle.txt +100644 d35e8a04fe28b095b5405ae2e0b09e3ab448bf63 0 Documentation/git-cat-file.txt +100644 2b821f2a1d70fa108ce279135fd5028453a04fd9 0 Documentation/git-check-attr.txt +100644 034223cc5ace81dd0b63da44d79db5e83a1d492a 0 Documentation/git-check-ref-format.txt +100644 62d84836b8a0d77c2a6ea534566ff8462b0eb37a 0 Documentation/git-checkout-index.txt +100644 5aa69c0e12a6756fd6f79c117008a373f65ba5f5 0 Documentation/git-checkout.txt +100644 837fb08b7971a8b948dd7d039bab4a3c2e54cac7 0 Documentation/git-cherry-pick.txt +100644 74d14c4e7fc88e702e4781b22eb8ed5ce0480c6f 0 Documentation/git-cherry.txt +100644 670cb02b6cc035e4fbcf1a1016f66b7a85cd4ef7 0 Documentation/git-citool.txt +100644 7dcc1ba58c3879cb14ce243a4af00bca9e850799 0 Documentation/git-clean.txt +100644 26fd1b111798461b9150f1416721aa460f1ea525 0 Documentation/git-clone.txt +100644 feec58400b64c65c8f96f2d1834016455c89829e 0 Documentation/git-commit-tree.txt +100644 0e25bb862704eee4a22fe5349c04823d14ea9cba 0 Documentation/git-commit.txt +100644 28e1861094a1689cdb042df6b1d788620ffdf213 0 Documentation/git-config.txt +100644 75a8da1ca906aee4cc6a7d0c3ff19862b8e0fc2f 0 Documentation/git-count-objects.txt +100644 2da8588f4fd6edb842a9824181165b3f043ec87b 0 Documentation/git-cvsexportcommit.txt +100644 b7a8c10b8709108c1c8a0d14f661c179c2b4f22c 0 Documentation/git-cvsimport.txt +100644 c2d3c90d27084e7de7e0f7c37b40f130f6960244 0 Documentation/git-cvsserver.txt +100644 4ba4b75c1126d87c48935e7697e05f0d5210ad2d 0 Documentation/git-daemon.txt +100644 7fdda04bae34790eb2345427dda746e8bf097c1a 0 Documentation/git-describe.txt +100644 5c8c1d95a89b15e936816f486a8114cbc6788fb9 0 Documentation/git-diff-files.txt +100644 26920d4f63cd213ff17ab28d8dd0dbea94482147 0 Documentation/git-diff-index.txt +100644 8c8f35b7a762d42d3c45ff9f4eee1cb32d3917b6 0 Documentation/git-diff-tree.txt +100644 c53eba557d0a242a0d8553d9569ce8b2eb86331b 0 Documentation/git-diff.txt +100644 b974e2115b01f17f0ac809b691baf2f4e4d32169 0 Documentation/git-fast-export.txt +100644 c2f483a8d2aed8dc017f3172e2d5fff4bed2c450 0 Documentation/git-fast-import.txt +100644 47448da22eeebf51fe5829717df2dc7129a9b17e 0 Documentation/git-fetch-pack.txt +100644 d3164c5c88db6b9e02a4186c398e19c425bc204b 0 Documentation/git-fetch.txt +100644 7ba9dab5e6c0b32f927d24de800e17b71a06b84b 0 Documentation/git-filter-branch.txt +100644 1c24796d66d5aeaeeccfd152c69cddba1953fd6c 0 Documentation/git-fmt-merge-msg.txt +100644 727d84e6735417baa82fe7abff5b6945f6d6cef4 0 Documentation/git-for-each-ref.txt +100644 010d9e432231f41a179023df2e85610583b572cf 0 Documentation/git-format-patch.txt +100644 965a8279c1b17df6fbf82f4fbcadbd254049a7d5 0 Documentation/git-fsck-objects.txt +100644 d5a76472196a5e67bc6e62411d90377ec3b46e3a 0 Documentation/git-fsck.txt +100644 7086eea74a38b036130f61db362bae209a065e47 0 Documentation/git-gc.txt +100644 84f23ee525336fc2bdd289991b97eafecddc14b2 0 Documentation/git-get-tar-commit-id.txt +100644 fa4d133c1bccc088d3c8da65bce4e15cafd394aa 0 Documentation/git-grep.txt +100644 0e650f497bd456e633334a91bd929053a08eb0d3 0 Documentation/git-gui.txt +100644 ac928e198e75595a6fcd4e83b89aaf68987bd420 0 Documentation/git-hash-object.txt +100644 f414583fc48e85e4785fbf5f9431bb81a96ccd9d 0 Documentation/git-help.txt +100644 e7c796155fdd0ad644decf5dc488c6d780a2d164 0 Documentation/git-http-fetch.txt +100644 aef383e0b142bd603b77620cad720c102d70c4b7 0 Documentation/git-http-push.txt +100644 b3d8da33ee64730794821440c287f30c4bb85789 0 Documentation/git-imap-send.txt +100644 4b5c743c1e5f11281e2b9df7508d57e9878ee5d2 0 Documentation/git-index-pack.txt +100644 1fd0ff2610a1375bcf0defe2a234b2dee1a7997a 0 Documentation/git-init-db.txt +100644 71749c09d309f4cae2da9788969359d2620224a9 0 Documentation/git-init.txt +100644 22da21a54f625c434216945889127ec283d3d09f 0 Documentation/git-instaweb.txt +100644 05cbac56aced6ad27f36fe63f8f536e794794f9f 0 Documentation/git-log.txt +100644 602b8d5d4de8f7649cb88e6622108c012f484933 0 Documentation/git-lost-found.txt +100644 9f85d60b5fb6d6ae1b4d8c2e65a6131cbe21450b 0 Documentation/git-ls-files.txt +100644 abe7bf9ff9eb9a3ddb1924938de071291520797a 0 Documentation/git-ls-remote.txt +100644 4c7262f1cd82ca8d9ea6be638d23b18d9bba3738 0 Documentation/git-ls-tree.txt +100644 31eccea5bc0697ee461503734942429c2133ef3f 0 Documentation/git-mailinfo.txt +100644 5cc94ec53daf3057f57c993983d659543962abec 0 Documentation/git-mailsplit.txt +100644 1a7ecbf8f39381b0c7b2da513dfa26eacec15cf6 0 Documentation/git-merge-base.txt +100644 024ec015a3a3e0d3677a82e082e72a36c4572827 0 Documentation/git-merge-file.txt +100644 ff088c5c294527dd97c542012483aafe3ca64314 0 Documentation/git-merge-index.txt +100644 dc8a96adb00c0b674e12e071a4a56f89bfe8583d 0 Documentation/git-merge-one-file.txt +100644 dbb0c18668ff0fb60c31c12b02c27d92b430c24a 0 Documentation/git-merge-tree.txt +100644 2db88809898592c691166427efdd106d844d42d9 0 Documentation/git-merge.txt +100644 31570b1e27af6a603df98868c627da08d91c17cc 0 Documentation/git-mergetool.txt +100644 8bcc11443dce7322ac5b0fa70e07b2465f762615 0 Documentation/git-mktag.txt +100644 af19f06ed738bdecc7ab9a72a5c9a216b816f4c2 0 Documentation/git-mktree.txt +100644 9c5660275b326661bf7dc9a5162e5177b8a62b0f 0 Documentation/git-mv.txt +100644 6e77ab135353aaf713b638a70701717db1706c2c 0 Documentation/git-name-rev.txt +100644 8c354bd47014825de71243d73158b6b080ecb350 0 Documentation/git-pack-objects.txt +100644 5f9435e59b49fec1e37c65f1bfdc38be3704c4e5 0 Documentation/git-pack-redundant.txt +100644 a5244d35f49f10b7954d8fa52164663e3d167d4b 0 Documentation/git-pack-refs.txt +100644 cd43069874d59504627211e011250a3554aeee5a 0 Documentation/git-parse-remote.txt +100644 477785e13418e1971156f5210015da4ab9d77cab 0 Documentation/git-patch-id.txt +100644 8282a5e82b6e897ac501ef05c982d5e69415363f 0 Documentation/git-peek-remote.txt +100644 b5f26cee132622185457d92522fb932302dec97d 0 Documentation/git-prune-packed.txt +100644 54f1dab38de9e01d8452753ac6028875b91d5f6b 0 Documentation/git-prune.txt +100644 7578623edba9e2ddc5232f1a981bcb297182638d 0 Documentation/git-pull.txt +100644 050c3ddae2732fdf4cb9f3b0f798e3d2d190fa4e 0 Documentation/git-push.txt +100644 d4037de5124010e9c90dcc97e8b64e6011dbed21 0 Documentation/git-quiltimport.txt +100644 6f4b9b017f7b504a2b9e909639a61b1ef7750af0 0 Documentation/git-read-tree.txt +100644 59c1b021a6c410e1097df21d6d47365aec6689e2 0 Documentation/git-rebase.txt +100644 6b2f8c4de7c32927f270e561362d4766193986fa 0 Documentation/git-receive-pack.txt +100644 d99236e14d5238c936304029bd48efc6ac9dd024 0 Documentation/git-reflog.txt +100644 25ff8f9dcbe0db52675338f1429e9169052b9cf1 0 Documentation/git-relink.txt +100644 bb99810ec76f93ff1cdc59aacdf592c64419701a 0 Documentation/git-remote.txt +100644 38ac60947bc6c4cbfc8aae70a92f9163fefed442 0 Documentation/git-repack.txt +100644 e5bdb5533e61687874ad36d30534b2ac9e58d7cb 0 Documentation/git-repo-config.txt +100644 19335fddae2b706cd785258a8c02a5595c525667 0 Documentation/git-request-pull.txt +100644 89f321b414212a555f1e0fb0ff0ce6f4c180fd06 0 Documentation/git-rerere.txt +100644 6abaeac28cb70bcff809c803d732f79630c8046f 0 Documentation/git-reset.txt +100644 fd1de92e34b459cdc89928e1561ee6934cd63c19 0 Documentation/git-rev-list.txt +100644 2921da320d2b84df4d15ec2745e6d94093dd6907 0 Documentation/git-rev-parse.txt +100644 98cfa3c0d0f27e0cb603f07c76285f85ef07a771 0 Documentation/git-revert.txt +100644 4d0c495bc3ecb5482165a46956efe73dfdc5ee72 0 Documentation/git-rm.txt +100644 afbb294a7faadc7ff6d4039246dc1c085575cd4f 0 Documentation/git-send-email.txt +100644 399821832c2a5cd6a718a7dc37a87e6b5bc0b213 0 Documentation/git-send-pack.txt +100644 18f14b5be89b4e0240f59b13313308f3c09d012c 0 Documentation/git-sh-setup.txt +100644 ff420f8f8c52eb598976a134916000da9b8f3976 0 Documentation/git-shell.txt +100644 7ccf31ccc401fd35a0ed65667be001805436249b 0 Documentation/git-shortlog.txt +100644 d3f258869f5d441bea16b46d8030eb64ecb7df99 0 Documentation/git-show-branch.txt +100644 e3285aacfd310cc269cdb03aa9243c939c24def7 0 Documentation/git-show-index.txt +100644 9a4389981ca067633d773e28393a1d72ac6552ae 0 Documentation/git-show-ref.txt +100644 1642cfd8236a5b57420f67da580b42066afaa4f6 0 Documentation/git-show.txt +100644 7d50d74cc9a945f0dd82b0c26509bf0392eff837 0 Documentation/git-stash.txt +100644 84f60f3407499c40a8e0caadf9d40ed5e9b8386b 0 Documentation/git-status.txt +100644 7508c0e42d2cd50ac522fc80a3a866411b7b51c5 0 Documentation/git-stripspace.txt +100644 35efeefb3056ac69cf02689dc338956340e9efc9 0 Documentation/git-submodule.txt +100644 f230125a81baab9f13bf84e3543b63fc77a8e827 0 Documentation/git-svn.txt +100644 210fde03a12cd757769f81754e789a2a5934f02c 0 Documentation/git-symbolic-ref.txt +100644 046ab3542bab4048fe07c8a6718d63f9cd9e3791 0 Documentation/git-tag.txt +100644 a5d9558dd1eabd71e838026721d707c5f1ecc369 0 Documentation/git-tar-tree.txt +100644 a96403cb8cb720dbf094b06a0dc0b430147298fc 0 Documentation/git-tools.txt +100644 995db9feadf68df6f22de745d90790a145128e44 0 Documentation/git-unpack-file.txt +100644 36d1038056101a459a33e32b6729d75e03f127ce 0 Documentation/git-unpack-objects.txt +100644 1d9d81a702d26706047ae6ea29b4ca62ebe59460 0 Documentation/git-update-index.txt +100644 9639f705afafab6fcf0cd21ad2693627ab42f66d 0 Documentation/git-update-ref.txt +100644 35d27b0c7f0e4b7a1d0851140958e71fabb0e6bc 0 Documentation/git-update-server-info.txt +100644 bbd7617587084b0c66fd8e0b9f623cac50be2c03 0 Documentation/git-upload-archive.txt +100644 b8e49dce4a19a4d7083459468f27c273c1d91fea 0 Documentation/git-upload-pack.txt +100644 3647dd6c8f9c74a688f7a143119386ba89a8f13d 0 Documentation/git-var.txt +100644 c8611632d1d501d57eb7000de0ec3c3b36810b80 0 Documentation/git-verify-pack.txt +100644 ba837df4bc66e2b828fcd49c94f35957c27322df 0 Documentation/git-verify-tag.txt +100644 36afad8d4e0d67a8d9dd33d3bc590789e9f6604d 0 Documentation/git-web--browse.txt +100644 cadfbd90403766d44598c8d96d89dc5a0e4e2ef8 0 Documentation/git-whatchanged.txt +100644 26d3850e7317c22dcf0999e0c4a6afe9a5ea2e03 0 Documentation/git-write-tree.txt +100644 44ea35e949dbbc0e5785ba2831072059881058f8 0 Documentation/git.txt +100644 d7b41142d2c843bc5460f03c73c609b1c53ff4a6 0 Documentation/gitattributes.txt +100644 29e5929db22257346a2bed16cbd5ca6531698676 0 Documentation/gitcli.txt +100644 49179b0a00fad1ecda1fdf0537ccbce77f5fc494 0 Documentation/gitcore-tutorial.txt +100644 aaa7ef737a4c190c60e37e2849ce42f3bdb5dda7 0 Documentation/gitcvs-migration.txt +100644 2bdbc3d4f6a97c1a1d970f0c5f27222ef9e31274 0 Documentation/gitdiffcore.txt +100644 565719ed5f8516e17229ec11bbec5e1354580397 0 Documentation/gitglossary.txt +100644 046a2a7fe7cf8ec301d3a20f7ebc587a09d210e3 0 Documentation/githooks.txt +100644 59321a2e82b1e141746d94c439452b52b84994ad 0 Documentation/gitignore.txt +100644 e02ecf57444df14d61d82dcf2f9e0c3f6b990b91 0 Documentation/gitk.txt +100644 f8d122a8b90ca7cb4920768ca23fd9a27574ffdf 0 Documentation/gitmodules.txt +100644 a969b3fbc3efc99ce490455b93c8bfb912994e2e 0 Documentation/gitrepository-layout.txt +100644 660904686c656fd00078aa272d0b9a5a198e1833 0 Documentation/gittutorial-2.txt +100644 48d1454a90cf9453e5e3c9fa01b3dbc369a58f1f 0 Documentation/gittutorial.txt +100644 9b4a4f45e900a96c4ddeb214816877f39cca15a5 0 Documentation/glossary-content.txt +100755 34aa30c5b9ffc617e1519878317c2ae83bed6a6a 0 Documentation/howto-index.sh +100644 4357e269131fad960367534ae4161fe078fee30a 0 Documentation/howto/maintain-git.txt +100644 554909fe08de380aa02f2bf37f0f3b43b0233f4b 0 Documentation/howto/rebase-and-edit.txt +100644 d214d4bf9d0e539c6bf58ba24dcd12aabbaea1d8 0 Documentation/howto/rebase-from-internal-branch.txt +100644 48c67568d3418b2d6608f362f4b76e02ec450abc 0 Documentation/howto/rebuild-from-update-hook.txt +100644 323b513ed0e0ce8b749672f589a375073a050b97 0 Documentation/howto/recover-corrupted-blob-object.txt +100644 e70d8a31e7b05e8efc70c6a56f476324065d57a6 0 Documentation/howto/revert-branch-rebase.txt +100644 6d3eb8ed00e1779efce8abe201d37c8cff07ec29 0 Documentation/howto/separating-topic-branches.txt +100644 40327486084ac02874faff70fd100b619af83214 0 Documentation/howto/setup-git-server-over-http.txt +100644 697d9188850e9a685045da5bd37844b02978752d 0 Documentation/howto/update-hook-example.txt +100644 4e2f75cb6167633c97ec1981d2b6659368cc0170 0 Documentation/howto/use-git-daemon.txt +100644 0953a50b693307976977c81a4c0b611fd5dfb490 0 Documentation/howto/using-merge-subtree.txt +100644 fb0d7da56b902217f8f1f4d4bc85186d6bf0dc4c 0 Documentation/i18n.txt +100755 35f440876ed182de319b6d3f0b8296b1a1ede29d 0 Documentation/install-doc-quick.sh +100755 2135a8ee1f4f56a8c799437949ba76d7526164c0 0 Documentation/install-webdoc.sh +100644 4065a3a27a38be73132b9f509e1d63546b3fddef 0 Documentation/manpage-1.72.xsl +100644 48ce747cf4dad592d642735856eb156e93d6cf30 0 Documentation/merge-config.txt +100644 007909a82fe77325e46c54799d00dc78493a47f9 0 Documentation/merge-options.txt +100644 1276f858ade29bec40716d19cf56fe6e3882fc25 0 Documentation/merge-strategies.txt +100644 c11d4957714db202a012209e2437b9e050a28ae0 0 Documentation/pretty-formats.txt +100644 6d66c74cc11e6622892061f8328d04dfe38f87bf 0 Documentation/pretty-options.txt +100644 00a8d210476089257be3d09ac8a16d1f8e1dd8dc 0 Documentation/pull-fetch-param.txt +100644 3aa38097e6350a02c50873d5c670e108003fab22 0 Documentation/rev-list-options.txt +100644 8aa891daee050f03cf265a6ea991ff9ebb60815e 0 Documentation/technical/.gitignore +100644 43dbe09f735525b0a1549ccfb4de2f2ca87252a0 0 Documentation/technical/api-allocation-growing.txt +100644 7ede1e64e5d40ec8f742e900d7273d6f961605e2 0 Documentation/technical/api-builtin.txt +100644 1d52a6ce14416c7308f6c2c5d6a3dd2a43184e91 0 Documentation/technical/api-decorate.txt +100644 20b0241d30026747391fa4b6b38de5cf959cee70 0 Documentation/technical/api-diff.txt +100644 5bbd18f0206604416c3833b7541a5b55b7e63976 0 Documentation/technical/api-directory-listing.txt +100644 9d97eaa9dee99eef7e66072c4c51cfff3000bba3 0 Documentation/technical/api-gitattributes.txt +100644 a69cc8964d585db41b1907a8ce7cb8d0a9511ef2 0 Documentation/technical/api-grep.txt +100644 c784d3edcb2537b84bfb5db3da55faaf45995155 0 Documentation/technical/api-hash.txt +100644 e9559790a32185b1d4ac8ae72881f4f63f082538 0 Documentation/technical/api-history-graph.txt +100644 adbdbf5d75d8e17e38e1ba0e3694b4ff210f5799 0 Documentation/technical/api-in-core-index.txt +100644 af7cc2e395f1399830f9eacc52468376b216fb86 0 Documentation/technical/api-index-skel.txt +100755 9c3f4131b8586408acd81d1e60912b51688575ed 0 Documentation/technical/api-index.sh +100644 dd894043ae8b04269b3aa2108f96cb935217181d 0 Documentation/technical/api-lockfile.txt +100644 03bb0e950dd1616b00f950f83263835c57bfa70a 0 Documentation/technical/api-object-access.txt +100644 539863b1f920f8f34ad9272907cbacbd35a7fcbd 0 Documentation/technical/api-parse-options.txt +100644 e8a1bce94e05f06c5b2aa51d2b610fb9da15d0bb 0 Documentation/technical/api-quote.txt +100644 073b22bd83badb5aada47e061bb29e48d5f95518 0 Documentation/technical/api-remote.txt +100644 996da0503acfa3e3a0ed0f57a951d0fbc1500fb8 0 Documentation/technical/api-revision-walking.txt +100644 75aa5d49234ec36857a7c8d2f3900001af5cbcde 0 Documentation/technical/api-run-command.txt +100644 4f63a04d7db0e7b578c5034c2856ba95a7ef5739 0 Documentation/technical/api-setup.txt +100644 a9668e5f2d2b1a7ffac45e4111ca6d8a4818af2b 0 Documentation/technical/api-strbuf.txt +100644 293bb15d206e71f57e906b33ca27ee05e3429521 0 Documentation/technical/api-string-list.txt +100644 e3ddf912841298d6317a682a29cbaf628e86f156 0 Documentation/technical/api-tree-walking.txt +100644 6296ecad1d65511f943fcd82ded188954a33b052 0 Documentation/technical/api-xdiff-interface.txt +100644 1803e64e465fa4f8f0fe520fc0fd95d0c9def5bd 0 Documentation/technical/pack-format.txt +100644 103eb5d989349c8e7e0147920b2e218caba9daf9 0 Documentation/technical/pack-heuristics.txt +100644 9cd48b48597f9b7e822fc3d81e0bc556d6631b02 0 Documentation/technical/pack-protocol.txt +100644 6bdf034b3af55c8d881fee9153d5cd1824660692 0 Documentation/technical/racy-git.txt +100644 681efe42190fa28f8e6bc8f1eb569bfcf160ed4b 0 Documentation/technical/send-pack-pipeline.txt +100644 559263af485f139d6c33d982bed9342aa4110e50 0 Documentation/technical/shallow.txt +100644 24c84100b0790be22330464e01ea876e5d30fc9a 0 Documentation/technical/trivial-merge.txt +100644 504ae8a53bca42d7c9ec560b65ddfe14699387a4 0 Documentation/urls-remotes.txt +100644 fa34c6747194aaecf9e8124462129b8bbc9ae7d4 0 Documentation/urls.txt +100644 339b30919e6cd9791a5cc30b93395a88fb5e9d96 0 Documentation/user-manual.conf +100644 00256ca57cc7453ef4a0dce90169078ee96e95e3 0 Documentation/user-manual.txt +100755 cb7cd4b53827fa6820e84b1318572d0115b3b17f 0 GIT-VERSION-GEN +100644 7d0c2c2f865d6ed969038e7543dbeb8933723ec3 0 INSTALL +100644 52c67c1a472455dcce5c19a21bbfd0520ff7dd26 0 Makefile +100644 548142c327a6790ff8821d67c2ee1eff7a656b52 0 README +120000 b9a53c3416991b66e1d35c2bbf663b48340b0041 0 RelNotes +100644 0d561246e0a958d9a7284409b1900a82876eebf3 0 abspath.c +100644 ccb1108c94436035d0da8b1d6f08f859b68294a3 0 alias.c +100644 216c23a6f854c614d38c743cd7687a37f304161b 0 alloc.c +100644 13029619e5ec34bac4ba61a6fc08800ab36f4a1b 0 archive-tar.c +100644 cf285044e3576d0127c3215cb1253443d67517dc 0 archive-zip.c +100644 f834b5f51f4cf5d3b73d21dfd99198caef3b19f8 0 archive.c +100644 0b15b35143fffcc13764e4e668ee452b191cc609 0 archive.h +100644 9e3ae038e818f4e21bc50f864fc5204f6fa44daa 0 arm/sha1.c +100644 3952646349cf9d033177e69ba9433652a378c0e9 0 arm/sha1.h +100644 8c1cb99fb403875af85e4d1524d21f7eb818f59b 0 arm/sha1_arm.S +100644 17f6a4dca521d9690377f2e93a0192d8a874d2ad 0 attr.c +100644 f1c2038b0923d3130937eef965667204a8634e6d 0 attr.h +100644 b88270f90844095b3d352cc4213cbebd95a7f420 0 base85.c +100644 bd7d078e1ae5fe4ce0a16fda62a2c1743237941b 0 blob.c +100644 ea5d9e9f8b63be2c7048d19ee53feb06b0795c80 0 blob.h +100644 b1e59f2196b933ab7169a30efc5d1d340f8f9c5c 0 branch.c +100644 9f0c2a2c1fab9a312f436880956da0973c68ead8 0 branch.h +100644 fc3f96eaefff91e4e85adb92162716939f0ecd72 0 builtin-add.c +100644 fc43eed36b55e4966796490b8c0a02fae790229c 0 builtin-annotate.c +100644 2216a0bf7cd53adc31346f66a3b9786a1d688bad 0 builtin-apply.c +100644 22445acbfc5279f391ac6afa855b21064ec54535 0 builtin-archive.c +100644 8b6b09b10b8f9dcda0b7224f31c860bb803945f0 0 builtin-blame.c +100644 b1a2ad7a6b3b150cda8d031a87352a4daedc40ea 0 builtin-branch.c +100644 ac476e7a4b45fc55b6b6d1e4d02be0c35aba2c7b 0 builtin-bundle.c +100644 7441a56acdbefdd8044a406f4d756ce8a4f06644 0 builtin-cat-file.c +100644 cb783fc77e75515a02ed2268dfb37ee3bbd03749 0 builtin-check-attr.c +100644 fe04be77a9312c11fa054897c5982fa6c74b8e5e 0 builtin-check-ref-format.c +100644 71ebabf9903bd90b7da59c47f1c0819b5f25c538 0 builtin-checkout-index.c +100644 411cc513c65ba854221ad52dd6aeaaac7d213c9d 0 builtin-checkout.c +100644 48bf29f40a5e06fd588b34c468535e04abcf206b 0 builtin-clean.c +100644 e086a40b41810c30a4f5228daa4e38857dae84d5 0 builtin-clone.c +100644 7a9a309be0543da7d27e7710ef82271f2582e0a9 0 builtin-commit-tree.c +100644 f7c053a0106c2e42833d0d7c7255b7b636d09a93 0 builtin-commit.c +100644 91fdc4985d8e64fae12209174dd4aa2d887793e5 0 builtin-config.c +100644 91b5487478998e39bb8ae4a5cb667360cff82c9a 0 builtin-count-objects.c +100644 ec404c839b6542deb4e15ca342fd3c0afbbedd2e 0 builtin-describe.c +100644 9bf10bb37e2f56ec2a10239d7419db8fbb641745 0 builtin-diff-files.c +100644 17d851b29ee5de33e01745eabcd5cd735c30b352 0 builtin-diff-index.c +100644 415cb1612f5322da89850874ba81885e41808678 0 builtin-diff-tree.c +100644 7ffea975059f9e13b07ca680e6707ffc14973f90 0 builtin-diff.c +100644 070971616dbb12d005c5c9a1f82cc5b0c5391f62 0 builtin-fast-export.c +100644 7460ab7fce2a4e6a7e014f448819140e2204ccb7 0 builtin-fetch--tool.c +100644 273239af3be61736ee4ff484d628950c4de7311a 0 builtin-fetch-pack.c +100644 7eec4a0e43ad5760f1060a7d5bcf2a5083015130 0 builtin-fetch.c +100644 df02ba7afdd615492361a1897a9dedd6ab233c96 0 builtin-fmt-merge-msg.c +100644 445039e19c75e4c9321f7ee64289ef8201a25c14 0 builtin-for-each-ref.c +100644 6eb7da88d3e8591a8c544acc61a42e00babff120 0 builtin-fsck.c +100644 fac200e0b08360625afc81b02913128c9b87f486 0 builtin-gc.c +100644 631129ddfd0ffe06f919882d22cfc494d9553f50 0 builtin-grep.c +100644 3a062487a7eacd01ed824b46a9124dd343cd2e60 0 builtin-http-fetch.c +100644 baf0d09ac4ea372b4015d399560a133b401b55cc 0 builtin-init-db.c +100644 f4975cf35f7f1555739f7657ee62ed983d18cb84 0 builtin-log.c +100644 e8d568eed7ab700bc338af8f589d2f61e81f323c 0 builtin-ls-files.c +100644 c21b841e7c5e8d27a6e66e7f70786d77aa4653b5 0 builtin-ls-remote.c +100644 d25767a1f7eb0a8b45bc1eed6b9aa95de0847f18 0 builtin-ls-tree.c +100644 f974b9df968c74c5d62d58b2a09493e6abb4322e 0 builtin-mailinfo.c +100644 71f3b3b8741e505fc652e6c74c75972f19211f71 0 builtin-mailsplit.c +100644 3382b1382a7dcbd525126a35209072da4b4d8041 0 builtin-merge-base.c +100644 3605960c2d9692514a6df0f344f3c3269cf1de3c 0 builtin-merge-file.c +100644 8f5bbaf402e020e308e7af9cedb7df1b2ec5a2b7 0 builtin-merge-ours.c +100644 43e55bf90154c51b94527b2ab7eb7c60fe36e9ec 0 builtin-merge-recursive.c +100644 dde0c7ed33118ff8d0cc421e8a0366e342c6d011 0 builtin-merge.c +100644 4f65b5ae9baf66953e79886fd93fe31786b24d36 0 builtin-mv.c +100644 85612c4dcb719b460623204046e35486e9d9fe97 0 builtin-name-rev.c +100644 2dadec1630c266bbaf42e84810f7059ed5c43b1e 0 builtin-pack-objects.c +100644 34246df4ec946273d9f42e6f0848b02d8510beea 0 builtin-pack-refs.c +100644 10cb8df8457fd5f2ba9be62ecd0f9384e21c3e63 0 builtin-prune-packed.c +100644 947de8cf258c73d8a327ef3a1daed417ba533f1b 0 builtin-prune.c +100644 c1ed68d938f67343c6938cfef54d5ff69a522a63 0 builtin-push.c +100644 72a6de302f88728af17ce5c5c6983c5267afc6f6 0 builtin-read-tree.c +100644 0c34e378199064e87aa09caf0fa0a2346333ec69 0 builtin-reflog.c +100644 54d1c3e3d16b2cebcff0c6c57d98756e48472b67 0 builtin-remote.c +100644 dd4573fe8dcd9dc8edd5a7d41bc8daa83034ee7e 0 builtin-rerere.c +100644 c24c21909194014b467c86fd3598796e7db576b3 0 builtin-reset.c +100644 893762c80f4910fadf2d6df414bd835cccb7faaa 0 builtin-rev-list.c +100644 9aa049ec170b0125fddde29adda3c720c8a7b8ee 0 builtin-rev-parse.c +100644 e9da870d22c14c32a0e0a6cb71b933c79a2d8b53 0 builtin-revert.c +100644 ee8247b08cd007f73d5dfffa560a9efe33d327b9 0 builtin-rm.c +100644 7588d22885d0af24ae80f1d687ccd097fe365021 0 builtin-send-pack.c +100644 d03f14fdad3d17dde06734d78ddb4aade6ed4f2b 0 builtin-shortlog.c +100644 233eed499d0b8790781326ff0455bdc7f09fe4d4 0 builtin-show-branch.c +100644 add16004f11375b1ad2b97f9b1bf1ced5c437f81 0 builtin-show-ref.c +100644 c0b21301ba4c126a49ed38b6983756b99a25aae0 0 builtin-stripspace.c +100644 bfc78bb3f6eff2f8e39649b9649ae7263f143ad9 0 builtin-symbolic-ref.c +100644 325b1b2632e44121c23bc6df556bf3aa4e32ba04 0 builtin-tag.c +100644 f4bea4a322c26a54734286073c5e67444555c2d9 0 builtin-tar-tree.c +100644 a8918666655bb91f952ccdac18715bd9ba4a09f2 0 builtin-unpack-objects.c +100644 38eb53ccba2b97a0fccf50d6ba0b7424fe2d1bcb 0 builtin-update-index.c +100644 56a0b1b39cf4c4fc51dbbff256240655bc36a038 0 builtin-update-ref.c +100644 a9b02fa32f372a6810867c10560a20d58b5b2a91 0 builtin-upload-archive.c +100644 f4ac595695b1fff1317ff7d14ea9427780327aea 0 builtin-verify-pack.c +100644 729a1593e61d87ad4596f07e7faedac81de64e81 0 builtin-verify-tag.c +100644 52a3c015ff8e4611522bd41078bdb2934d288d35 0 builtin-write-tree.c +100644 f3502d305e4f65e9707fe8b738f64be6e49f7f84 0 builtin.h +100644 00b2aabefca49b634f49143523ee31556baa7777 0 bundle.c +100644 e2aedd60d6ad1482bb6da173c853e6ba4805c8d7 0 bundle.h +100644 5f8ee87bb1c446341b640c2f978a658d6bfcfcd0 0 cache-tree.c +100644 cf8b790874c4a4f5890b360c237ccdd4a5a03de4 0 cache-tree.h +100644 2475de9fa837596303284157e08b3080d64351ee 0 cache.h +100755 d6fe6cf1749ebcd6189fa36cbb4e14a532d2d17b 0 check-builtins.sh +100644 00d92a16631a80ff8ec4e995dafcd3e55434fad5 0 check-racy.c +100755 a1c4c3e8d845e8e791d7df0c1387e1b2262b5ecf 0 check_bindir +100644 fc0b72ad59b13e4bd86372e5e81b4f400c99d26e 0 color.c +100644 6cf5c88aaf8d0e38e2853e6fd212e3cdd6c180cb 0 color.h +100644 9f80a1c5e3a461afd11966625589684d61187911 0 combine-diff.c +100644 3583a33ee90647d8e6ded02643eb75753760d94f 0 command-list.txt +100644 dc0c5bfdab7296bf7febb6f1b1aad64550838c15 0 commit.c +100644 77de9621d9c926c6fb8a2bf9ca81c6c376a2ad41 0 commit.h +100644 1f4ead5f981688ee9e29ae2ee281e3904c9131f6 0 compat/fnmatch.c +100644 cc3ec379400e9334a37cf3f07a61a77f264da885 0 compat/fnmatch.h +100644 b5ca142fedf2ac0e0cedde1011ab385f65010fdf 0 compat/fopen.c +100644 069c555da47ea168eea937fcc2d788294bf92ef5 0 compat/hstrerror.c +100644 f44498258d4c2a0ebd1379ed818d9d04b56f0761 0 compat/inet_ntop.c +100644 4078fc0877ca99c82152acdd6b7a9eef70a9f8a4 0 compat/inet_pton.c +100644 cd0d8773641f2fdc808d8b246a8dd2bcd0e5814d 0 compat/memmem.c +100644 772cad510d5d260fdf33b4f7d6ff79f9f3367b05 0 compat/mingw.c +100644 290a9e6f822df97984b9f769508aab36419eaf02 0 compat/mingw.h +100644 34d4b49818b0896b9db19b2b1387f142cbbbd42b 0 compat/mkdtemp.c +100644 c9d46d174259f42a3e2a2eb073475aba517044be 0 compat/mmap.c +100644 978cac4ec91e6bb2f81539d85422bb37e4941a51 0 compat/pread.c +100644 d93dce2cf8fa33bd1fbefe131d2e41a6b954da61 0 compat/qsort.c +100644 87b33e46697b9a5dd9a9e8391ca3607f7e2ff569 0 compat/regex.c +100644 6eb64f14020db0a20ba596de5b58b3c552157f16 0 compat/regex.h +100644 3a22ea7b751efb768d72afa2f97fd963e10eec7e 0 compat/setenv.c +100644 580966e56a3455b9970b7eef33143c1e9c57d41c 0 compat/snprintf.c +100644 26896deca64c531f57b4c48ea92134f876b8e537 0 compat/strcasestr.c +100644 4024c360301ebe7d58ac5b84dcbb692341b649ed 0 compat/strlcpy.c +100644 5541353a77a22d48ccebd20ede2f510ae20d1492 0 compat/strtoumax.c +100644 eb29f5e0849370afe90c400271fea12e0f9090aa 0 compat/unsetenv.c +100644 e2d96dfe6f75213de567174261d9aeba3e663d9d 0 compat/winansi.c +100644 53f04a076a7275965090edd4ca2a34652c4f5679 0 config.c +100644 b776149531025c85f5665d971e6e072f0cc64893 0 config.mak.in +100644 7c2856efc92ca55e3cf03fcf1c72ffb70318f7c3 0 configure.ac +100644 574f42fa47ffa69328217eb25afee6f85db9595e 0 connect.c +100644 05f291c1f1d3d1018f390618816f94d0cd58951b 0 contrib/README +100644 fada5ce909876168f68a85c8ca9a8bc269048acb 0 contrib/blameview/README +100755 1dec00137b2dd8a888a962edd62f01aad89e4186 0 contrib/blameview/blameview.perl +100755 30d870187e64e33ed430dc1fab1ea37036a07f58 0 contrib/completion/git-completion.bash +100644 4009a151deceb45030fb26c5bfcf1b75a423a493 0 contrib/continuous/cidaemon +100644 b8f5a609af464f3af8b624246cc69eb335ce81d1 0 contrib/continuous/post-receive-cinotify +100644 90e7900e6d7aff2fadf9ba04f8d982733493411c 0 contrib/convert-objects/convert-objects.c +100644 9718abf86d8cd36ddae1eae8cf2337e35b927959 0 contrib/convert-objects/git-convert-objects.txt +100644 c531d9867f6c223be1daf0f6da7538feb11966d8 0 contrib/emacs/.gitignore +100644 a48540a92b4aa5140a87469b36c1f9b3d8e46e7f 0 contrib/emacs/Makefile +100644 4fa70c5ad47fcd717d9cbdb23a8142f89227f630 0 contrib/emacs/git-blame.el +100644 c1cf1cbcc014e5d6c01a1c33efa2d7bd3b76df88 0 contrib/emacs/git.el +100644 b8f6be5c0af64dfbe7e136f984404f22aea68130 0 contrib/emacs/vc-git.el +100755 1a7689a48f07a6ed2bb156f745bfea19a10e3eb9 0 contrib/examples/git-checkout.sh +100755 01c95e9fe8a19afcf331ed5ffd47eea478886213 0 contrib/examples/git-clean.sh +100755 547228e13ce60e575d0b4a10a322edfff6c0622c 0 contrib/examples/git-clone.sh +100755 2c4a4062a5317c51601fc4c644c96a7f75e1ef2c 0 contrib/examples/git-commit.sh +100755 e44af2c86d8e7e44bc79aafcc8ccef3806804720 0 contrib/examples/git-fetch.sh +100755 1597e9f33f5e001995085639a448f1214010b561 0 contrib/examples/git-gc.sh +100755 fec70bbf88c614a2dadfc40950fdd7abdf7f2c63 0 contrib/examples/git-ls-remote.sh +100755 29dba4ba3a57c15bd430bd23c1cebe78e6dc03be 0 contrib/examples/git-merge-ours.sh +100755 e9588eec33ba5b64d186ff048bb040c18c57e6bc 0 contrib/examples/git-merge.sh +100755 36bd54c985080f8dd5558a3e7a4e19ede9fbab93 0 contrib/examples/git-remote.perl +100755 4f692091e73bf633cf986ba2c9bed38bc2c78538 0 contrib/examples/git-rerere.perl +100755 bafeb52cd113ad8a07ffd1912191f2bc17a7ef7a 0 contrib/examples/git-reset.sh +100755 0ee1bd898ecbb725d13385408b4ed4bcb413f503 0 contrib/examples/git-resolve.sh +100755 49f00321b28833c24ebb78ea2104f34091d43017 0 contrib/examples/git-revert.sh +100755 a13bb6afec2fe5e0b5249523ec8c62d8e517de88 0 contrib/examples/git-svnimport.perl +100644 71aad8b45bd4c5f59c2ce3746cb3299821729c2a 0 contrib/examples/git-svnimport.txt +100755 e9f3a228af472c932f6cec5fa25ae49cd841b239 0 contrib/examples/git-tag.sh +100755 0902a5c21adc4123e36856f73acc1409e17eb0ac 0 contrib/examples/git-verify-tag.sh +100755 f9fef6db28dbba0ef67589f05eeb937760d2facf 0 contrib/fast-import/git-import.perl +100755 0ca7718d0518db2e559ecd17eb6f7f57338b80fd 0 contrib/fast-import/git-import.sh +100755 6ae0429c2dde435f8ae33991ad10f40485aefdc6 0 contrib/fast-import/git-p4 +100644 9f97e884f5c3cae9e89164c9590959ba487a89bd 0 contrib/fast-import/git-p4.bat +100644 b16a8384bcfbfe33dc33e1076c64f5d36e75e803 0 contrib/fast-import/git-p4.txt +100755 23aeb257b9557cb146586868084ce1b20d7e7ac8 0 contrib/fast-import/import-tars.perl +100755 c674fa2d1b5c6ab47ebb5e828c427c6d47bb50fc 0 contrib/fast-import/import-zips.py +100755 4c99dfb9038ca034d86b72cbe342373d12ae8cc6 0 contrib/gitview/gitview +100644 77c29de305fabc518edf060b0e6634d9c5c9f71e 0 contrib/gitview/gitview.txt +100755 7b03204ed18500756ba55818f0808b52db68d048 0 contrib/hg-to-git/hg-to-git.py +100644 91f8fe6410c06308b6dce47ca28f4c1b8536c5bb 0 contrib/hg-to-git/hg-to-git.txt +100644 41368950d6b29121089ee9239b8e07ece209a31e 0 contrib/hooks/post-receive-email +100644 0096f57b7e09f33108d7177405d02eec05811c03 0 contrib/hooks/pre-auto-gc-battery +100644 dab7c8e3a1829b31f2b10eafe8becf0f067b5a05 0 contrib/hooks/setgitperms.perl +100644 d18b317b2f018d1d1a5a9677a7bdaf8956d65186 0 contrib/hooks/update-paranoid +100644 b9892b679320d17c1e9812633d892a7e057865aa 0 contrib/p4import/README +100644 0f3d97b67eef3108728265e26f5d79c4526d11ac 0 contrib/p4import/git-p4import.py +100644 9967587fe6bef0cf7cc3e93467bd179f6f58b032 0 contrib/p4import/git-p4import.txt +100644 f2b08b4f4a5ea29ac6125f5717fa661e11b7d5f9 0 contrib/patches/docbook-xsl-manpages-charmap.patch +100755 1cda19f66af96fa828de81cbb21817abca0c06ea 0 contrib/remotes2config.sh +100755 e27fd088be1bd3ecc3da116d5f32ce10d89897ac 0 contrib/stats/git-common-hash +100755 4b852e2455bab324e3bd16e02ec712fbacbf34b0 0 contrib/stats/mailmap.pl +100755 f4a7b62cd9f1a397118b95792c04c2f70f910f9e 0 contrib/stats/packinfo.pl +100644 39f96aa115e0a20024d2f41138db6b2b8c6d5320 0 contrib/thunderbird-patch-inline/README +100755 cc518f3c890158d29e751e008436e0c3790746ee 0 contrib/thunderbird-patch-inline/appp.sh +100644 9e7881fea923e47c3c35028ebbc00bce395d4005 0 contrib/vim/README +100644 332121b40e9c54de9f55362cb57760136039e9fd 0 contrib/vim/syntax/gitcommit.vim +100755 7959eab902d28bb3307c542514ca4c5f49deee0f 0 contrib/workdir/git-new-workdir +100644 78efed800d4d64898d438d9590b01be008cfcd36 0 convert.c +100644 e54d15aced7595ccb11423b0de121db9051ad1f3 0 copy.c +100644 ace64f165e4a01fb99892e9b89e7df791a7f4ca1 0 csum-file.c +100644 72c9487f4fd9fcab5e02fc2dc6afd3cb7f9c036a 0 csum-file.h +100644 ee06eb7f48f1d3e818b3037369b4e056fe7e5be7 0 ctype.c +100644 4540e8df5ab8bc8ff66549144d7db2928e12199b 0 daemon.c +100644 35a52576c53e5e1406d40ed4402b8834a29b9f0e 0 date.c +100644 d9668d2ef94c73e4a7a5602011ff13a9fd9d8c6a 0 decorate.c +100644 1fa4ad9beb08f23888814b99183487ab85378bfd 0 decorate.h +100644 40ccf5a1e95f62d840a006274f7024fa43208b1c 0 delta.h +100644 a4e28df714b4834e5efe42fa3abb647711913d71 0 diff-delta.c +100644 e7eaff9a68ccbcc692522c9956f0dae9af45f3f1 0 diff-lib.c +100644 7d68b7f1bef1039b4996e662fb17968c4e3e3e79 0 diff-no-index.c +100644 cbf25473c594abfd1fc13473108dc9c15e2f1d15 0 diff.c +100644 50fb5ddb0bec02b0cd5498d6ecc37d44bf874476 0 diff.h +100644 31cdcfe8bcdae7df65b0387071846299a14bb7be 0 diffcore-break.c +100644 e670f8512558c38d9a9d6e754cfc609b042b1195 0 diffcore-delta.c +100644 23e93852d8c701760d56e7e728dba7c08367fbe8 0 diffcore-order.c +100644 af9fffe6e8e145b066157da8791c749257e7c8e9 0 diffcore-pickaxe.c +100644 1b2ebb40014d820fe4fb679509ab694d453be7b4 0 diffcore-rename.c +100644 cc96c20734bf4184970f5381416637cf6e45ea13 0 diffcore.h +100644 29d1d5ba31def46ba8b55905dc60773cc6cc167e 0 dir.c +100644 2df15defb6720a742282f24721233c4816deceb6 0 dir.h +100644 1f73f1ea7dfa6a14dedf384c99751e86c8121ff4 0 dump-cache-tree.c +100644 eebc3e95fe0c7e61f7c29fa5412ea9d4a5900f92 0 editor.c +100644 222aaa374b8268828e9d529a8afacb8830acc281 0 entry.c +100644 0c6d11f6a0c6fa5dbab2f36c4b4ad4de5aba4ac9 0 environment.c +100644 ce6741eb682b59ad638c7bee6ca31e2fcd53f281 0 exec_cmd.c +100644 594f961387240c221020c9ea0bccd8a39ff69595 0 exec_cmd.h +100644 7089e6f9e6c5fa9142f468e54afe7d33a6d2eec7 0 fast-import.c +100644 8bd9c32561e79d194d27fa10cc98a26aa2cb673c 0 fetch-pack.h +100755 63dfa4c475ae3632fc5cfd093d949a41683a5458 0 fixup-builtins +100644 797e3178ae279f444d2efa7e3758652ad0898dd7 0 fsck.c +100644 990ee02335a2e2693e32baa82b259c23843f2aa0 0 fsck.h +100755 a2913c2a2cd1ec158157ada3e2deb666892b734b 0 generate-cmdlist.sh +100755 da768ee7acc22e6480f0a067e109239561d43927 0 git-add--interactive.perl +100755 6aa819280ef99ccbbdeefde037e99fae3e6f0110 0 git-am.sh +100755 98f3ede566a6cb0c902ce84795f7de8f8afbe633 0 git-archimport.perl +100755 3cac20db79e1e408a321b0e9d272501985a3c49b 0 git-bisect.sh +100644 cf89cdf4598b3796724a85aa707f740245155cdc 0 git-compat-util.h +100755 6d9f0ef0f989133422cf8c0302e63dab15a999d5 0 git-cvsexportcommit.perl +100755 e2664ef01308fd0fb65d47b25e0ae73a65aa6262 0 git-cvsimport.perl +100755 b0a805c688f59af29e1f25b514d73f3991285dee 0 git-cvsserver.perl +100755 182822a24e214fe7e93a2df68fdda3dd40b5896d 0 git-filter-branch.sh +100644 6483b21cbfc73601602d628a2c609d3ca84f9e53 0 git-gui/.gitignore +100755 0ab478ef90aba9048a2de785d538da16e1bae431 0 git-gui/GIT-VERSION-GEN +100644 55765c8a3aa6b3702b230e8efe3c2ab47a6e73e5 0 git-gui/Makefile +100755 14b2d9aacd1d28084f195365b434747df2ddc95d 0 git-gui/git-gui.sh +100644 241ab892cd5b731f07571acf7a0ca3150a763f4f 0 git-gui/lib/about.tcl +100644 b6e42cbc8fe0a49c301335f78cc2941bd9d59870 0 git-gui/lib/blame.tcl +100644 777eeb79c1355ec49ce175cc5c33a13df6e41c97 0 git-gui/lib/branch.tcl +100644 6603703ea163d830c7de1478aa2dd737c4d9d499 0 git-gui/lib/branch_checkout.tcl +100644 3817771b944cc363205b86c91f7b4801c1d568f9 0 git-gui/lib/branch_create.tcl +100644 ef1930b4911591566be4561b8c17c24e1cfbfaad 0 git-gui/lib/branch_delete.tcl +100644 166538808f461275075e2b03c56ddc15b5813e1a 0 git-gui/lib/branch_rename.tcl +100644 ab470d12648c1dd3560b411e70ea210cc4381e3e 0 git-gui/lib/browser.tcl +100644 caca88831b7066c573917542d24a52e832dcff34 0 git-gui/lib/checkout_op.tcl +100644 56443b042c62bc10765aaf6484ad1077a843cb30 0 git-gui/lib/choose_font.tcl +100644 318078615862adab052d0d0f637f82176a0a785a 0 git-gui/lib/choose_repository.tcl +100644 c8821c146386f850c0794df70f605cd9f18dcff3 0 git-gui/lib/choose_rev.tcl +100644 dc2141192a21e7416268cc94beda78d6ceb8f86f 0 git-gui/lib/class.tcl +100644 40a710355751836e78b65101592b753266f507ca 0 git-gui/lib/commit.tcl +100644 c112464ec367a2db707a3f28ff6c588aefe7985f 0 git-gui/lib/console.tcl +100644 a18ac8b4308d8263a0688058524282b72bafe77a 0 git-gui/lib/database.tcl +100644 abe82992b6529cf49983029d85348df5d27ceaf5 0 git-gui/lib/date.tcl +100644 52b79e4a1f476c2ee9b65087f66a352a25ed0903 0 git-gui/lib/diff.tcl +100644 7f06b0d47f0fa214c98644757f99f8a036b9689e 0 git-gui/lib/encoding.tcl +100644 75650157e551e34dab650d89f3fa6d25afc91d6a 0 git-gui/lib/error.tcl +100644 334cfa5a1a59c320e86789ccf4ed3320584a0215 0 git-gui/lib/git-gui.ico +100644 3c1fce7475d362d1880d915ff4bdf168fda28593 0 git-gui/lib/index.tcl +100644 5ff76692f5eeeb51bcca0905385f51963d1e6531 0 git-gui/lib/logo.tcl +100644 5c01875b051305b5b40a17ac7a2245f69081f41b 0 git-gui/lib/merge.tcl +100644 ffb3f00ff0a992254804cc047b5a63ce82aa5bd9 0 git-gui/lib/option.tcl +100644 0e86ddac0981fbb575a7dd5294ddaed29f7c3917 0 git-gui/lib/remote.tcl +100644 c7b81486984d46a9dca59867c406a8e247d76313 0 git-gui/lib/remote_branch_delete.tcl +100644 38c3151b05c732d919943e44629bfc0a8c9fb617 0 git-gui/lib/shortcut.tcl +100644 78f344f08f34842c134b6b0e22b6eb8c49b1dbbd 0 git-gui/lib/spellcheck.tcl +100644 51d4177551b911c35cfd8004a36fca4259367d24 0 git-gui/lib/status_bar.tcl +100644 8e6a9d0a6010cf5efee069a2d488124bcbdb54cd 0 git-gui/lib/transport.tcl +100644 d7f93d045d1a2b3a14d2fdb4907697622b5973a8 0 git-gui/lib/win32.tcl +100644 117923f8860bb8f0f04c1664d8cbe38804a59831 0 git-gui/lib/win32_shortcut.js +100644 ddbe6334a258dae46b6c333d53590f3b920a9cab 0 git-gui/macosx/AppMain.tcl +100644 b3bf15fa1c1a41265460664417caf47265553a4f 0 git-gui/macosx/Info.plist +100644 77d88a77a7669667335cf6fd5767c8b40f3ce6e7 0 git-gui/macosx/git-gui.icns +100644 a89cf4496990737117d7558a7465f68aa058e465 0 git-gui/po/.gitignore +100644 5e77a7d7d2d9e82311c1573035fa020c8d08b38c 0 git-gui/po/README +100644 f20955c18520bf2b0cc8826da9e1dc93a5624ac3 0 git-gui/po/de.po +100644 89b6d51ea011d5b0fc8a343ffdda078d659571cb 0 git-gui/po/fr.po +100644 813199f782968cb0949d24119145e8b3e4a174cd 0 git-gui/po/git-gui.pot +100644 749aa2e7ec1b02e6af3427516b1197f77bd48795 0 git-gui/po/glossary/Makefile +100644 35764d1d22da45e90638b2db3e0bfbcb332e8696 0 git-gui/po/glossary/de.po +100644 27c006abb2adca055ebf7e44ce9a4ec351af19c5 0 git-gui/po/glossary/fr.po +100644 40eb3e9c07a4d39091972655d8a99110fd361be5 0 git-gui/po/glossary/git-gui-glossary.pot +100644 9b31f69152025e484ddf467d7884c2bf2140a894 0 git-gui/po/glossary/git-gui-glossary.txt +100644 bb46b48d6b84446a23c14af88c339fda7e417718 0 git-gui/po/glossary/it.po +100755 49bf7c5365130ec290948ee8abba28d757774381 0 git-gui/po/glossary/txt-to-pot.sh +100644 158835b5c1c9364735167e618af62272c8bb7a8d 0 git-gui/po/glossary/zh_cn.po +100644 28760ed97838d39effd035ab4f1159c0085221f8 0 git-gui/po/hu.po +100644 11cc79bb5ec9c8f1a158ceb457157705b04d4adf 0 git-gui/po/it.po +100644 28e6d6246b5a07bc0dfe29dde4727be0cf0ba40c 0 git-gui/po/ja.po +100644 b7c4bf3fdffb3d04b8c01b25e99a706e499de0d1 0 git-gui/po/po2msg.sh +100644 db55b3e0a69813cba16932212ee1b2ce0f5b2b9a 0 git-gui/po/ru.po +100644 4da687bb41f5471eaa6dd49c2ffae1eaa053ec68 0 git-gui/po/sv.po +100644 d2c686667163ec4cd83045efd429e3413564290e 0 git-gui/po/zh_cn.po +100644 53c3a94686813936445efbb055dc4f02885c70e9 0 git-gui/windows/git-gui.sh +100755 0843372b57371b62cd68f2818f634209f55d5395 0 git-instaweb.sh +100755 9cedaf80ceac1d4100adf3cfb152c76c7f945e4d 0 git-lost-found.sh +100755 645e1147dc886f2b1ca6d2020b44db746b082bf0 0 git-merge-octopus.sh +100755 e1eb9632660146396a0b5f3f2a410d8cb027ff9d 0 git-merge-one-file.sh +100755 93bcfc2f5dce418d00f26257788932d5c738785c 0 git-merge-resolve.sh +100755 94187c306ccb05d977f2bb35e81828130ab49a61 0 git-mergetool.sh +100755 695a4094bb4230341618bd6f16d0bea9bff2e826 0 git-parse-remote.sh +100755 75c36100a2f858bcf2663d2b4560654787963175 0 git-pull.sh +100755 cebaee1cc9dfc28d80173583b144a480be2f9bfd 0 git-quiltimport.sh +100755 4e334ba41dad3067394b79c15ebfe610b2d3e178 0 git-rebase--interactive.sh +100755 412e135c3ae88d76b5bdf3f08083b153da220a95 0 git-rebase.sh +100755 937c69a74858a8a3c63bb41a23705b579df1b3a3 0 git-relink.perl +100755 683960b04d6b743e687b2eb640d2b0e00ccfd313 0 git-repack.sh +100755 073a314c8043e0ff30afde65e012e356ff0d186f 0 git-request-pull.sh +100755 d2fd89907688a044ffe0d2520744e00a9b33c942 0 git-send-email.perl +100755 dbdf209ec0e7d6468c199d1905c3e7788a9cd246 0 git-sh-setup.sh +100755 d4609ed66e56dc6021c800d60286bec38615ff39 0 git-stash.sh +100755 b40f876a2ca9fe985cedc622ab28a9f461edc5ab 0 git-submodule.sh +100755 cf6dbbc42773fef394c27cd87109b69c3144753c 0 git-svn.perl +100755 384148a59fc492d8fb1d6ea4fc4532aa1e5ffc22 0 git-web--browse.sh +100644 37b1d76a08ca59f3de54e11890dce962403cf8d3 0 git.c +100644 c6492e5be2763eab81358424ff625a34a5ff2fba 0 git.spec.in +100644 e1b6045605865cbfc4ae0d57039111d5df825649 0 gitk-git/Makefile +100644 fddcb45817ed6839ba95965d7e57e9a2e04ae30a 0 gitk-git/gitk +100644 e358dd1903352f5cd65ac77e777ae14d6887adc0 0 gitk-git/po/.gitignore +100644 b9867bf8e05d06f7effb9919f00879f77690e185 0 gitk-git/po/de.po +100644 2cb148624723adf82fd7f4a361133dbc8909db93 0 gitk-git/po/es.po +100644 d0f4c2e19ac15b10c01d6df968f823914cdbba4a 0 gitk-git/po/it.po +100644 c63248e3752b8b479f75ad2fe772dd40f684be54 0 gitk-git/po/po2msg.sh +100644 f6b080df4cf313edaba241eb54c232e9f282a38c 0 gitk-git/po/sv.po +100644 26967e201aca8ea1c799e6b21cad468484753779 0 gitweb/INSTALL +100644 825162a0b6dce8c354de67a30abfbad94d29fdde 0 gitweb/README +100644 de637c0608090162a6ce6b51d5f9bfe512cf8bcf 0 gitweb/git-favicon.png +100644 16ae8d5382de5ffe63b54139245143513a87446e 0 gitweb/git-logo.png +100644 aa0eeca24786dbd5143354fc3bb5e8cdb3ef831f 0 gitweb/gitweb.css +100755 90cd99bf916135e5c0a9e1bd7d5e9ff45555c489 0 gitweb/gitweb.perl +100644 e2633f8376eb7b12706dcd4c698e2b3f6be2b433 0 graph.c +100644 eab4e3daba9812293d4e005c3ebe28f9a97744ce 0 graph.h +100644 f67d6716ea5f42c3384a7a4cb2eb973b02785fba 0 grep.c +100644 d252dd25f81526d9b8663b4d3c9585d69a901397 0 grep.h +100644 46c06a9552dac5475afc607c3fe2bf00801eb055 0 hash-object.c +100644 1cd4c9d5c0945994b84bb25edd6e4685cf76b5c5 0 hash.c +100644 69e33a47b9861df9ac12c354eae180b4f8fea857 0 hash.h +100644 3cb19628965685ce59a5377b81bef975851996e8 0 help.c +100644 68052888570af7d09535db8831b8cf3ef2881589 0 http-push.c +100644 9dc6b27b457a2979a95018679a0b885e6fb62d9a 0 http-walker.c +100644 1108ab4a3101fb4768cad420ccfdb52d87890a18 0 http.c +100644 905b4629a47789705c13745fd56ce0c91adea41b 0 http.h +100644 b35504a8d25594a8d243ae7490733eae5a262712 0 ident.c +100644 1ec131092109aa3fbed3cd20f10b56a864584a94 0 imap-send.c +100644 52064befdbbbdf671bd08e369a133d4f1fee03c1 0 index-pack.c +100644 7f03bd99c5b66afa6cc7fa11a2430301a3042656 0 interpolate.c +100644 77407e67dca97eb85274c69e2e7469e1d4d40b3b 0 interpolate.h +100644 c8b8375e4983794e601ba69a1c217a3c711835e9 0 list-objects.c +100644 0f41391ecc00eac324ea76de7654781c4fce094e 0 list-objects.h +100644 9837c842a3f215ebee7cbe9690e42e216fb5c76c 0 ll-merge.c +100644 5388422d091ede134d42406291989c49553f7428 0 ll-merge.h +100644 4023797b00fe21ecbabe3407ef8a12fca0690607 0 lockfile.c +100644 bd8b9e45ab46b8664c8b7016b33bee22f86c9e0d 0 log-tree.c +100644 59ba4c48b7966db34c6345a445ab0b10e235ac83 0 log-tree.h +100644 88fc6f394684436967002ca477eac1e084537348 0 mailmap.c +100644 6e48f83cedd13e24d50cddf47f037791ddc5ad4b 0 mailmap.h +100644 0fd6df7d6ed839eaed536bc332312c2688a6bbad 0 match-trees.c +100644 2a939c9dd835a7e7946eb1548e4cf637ae3ca329 0 merge-file.c +100644 7491c56ad25332fb4aae6a075bf0577a1d800c3b 0 merge-index.c +100644 f37630a8ad07709ae106ddde44a34daf6bad8b16 0 merge-recursive.h +100644 02fc10f7e622ba1c53065e7cf4563ff10af0c41f 0 merge-tree.c +100644 0b34341f711a903d4a12fe96dc6ef63e55fb2f5b 0 mktag.c +100644 e0da110a98d3a7376dc78df71fabc10fc5664296 0 mktree.c +100644 3f06b835675206912777a774d91c3ba611fa5a06 0 mozilla-sha1/sha1.c +100644 16f2d3d43ca8bee1eeb306308277bef8c707a972 0 mozilla-sha1/sha1.h +100644 0031d78e8c98a32d61cd0dc0f939a033e24ed890 0 name-hash.c +100644 50b6528001fe4bafdfe70126dc2078860c3d1969 0 object.c +100644 036bd66fe9b6591e959e6df51160e636ab1a682e 0 object.h +100644 f596bf2db5e0a0065e6856b8caa3ded8a134f74d 0 pack-check.c +100644 25b81a445c8fafe0c00ce30082b7d9a7c22ccf1e 0 pack-redundant.c +100644 848d311c2b2c651dbb14893c260584f00c639357 0 pack-refs.c +100644 518acfb370ad72be18399a3ac5e8ca17880281c9 0 pack-refs.h +100644 cd300bdff5b524a4d509ba5276e9ef21f443013d 0 pack-revindex.c +100644 36a514a6cf600e7e77794e50998a9d160e30c8e9 0 pack-revindex.h +100644 a8f02699366c87de960d7637e9f69c26c2241693 0 pack-write.c +100644 76e6aa2aad06545e7c44fc2c1e117ea668356ccf 0 pack.h +100644 6b5c9e44b4ded338ddb344ae454d83a685b7569a 0 pager.c +100644 71a7acf4e22bd12c0919f277410d6ec52dd5efc8 0 parse-options.c +100644 bc317e7512af7a1cc86641a651ae5415d28e71c4 0 parse-options.h +100644 ed9db81fa82c812c9ffa07f5a40540dbb15da0d3 0 patch-delta.c +100644 9349bc5580456b378d41da7cc2518e4fa9a7e81a 0 patch-id.c +100644 3be5d3165e0009761a0ca69e15e4a9132c6cfaff 0 patch-ids.c +100644 c8c7ca110ad34def12a3594a1560b3c3052eb701 0 patch-ids.h +100644 9df447bd6dcfaddf7f05fe5f0b624700ff1f40d7 0 path.c +100644 98b24772c7ebe838d513d8e24f3c8acff7839cb9 0 perl/.gitignore +100644 d99e7782002e01079b3866003cc8555b7e130e3f 0 perl/Git.pm +100644 b8547db2c64ac1242725b5f71fb646b5bca38ef3 0 perl/Makefile +100644 320253eb8e91eb1d914aa4e34f7d3af4649b9b39 0 perl/Makefile.PL +100644 11e9cd9a02eb3f85a9150c6fb06d1fc76abd9b09 0 perl/private-Error.pm +100644 f5d00863a6234c16db33637d19fefd2014780e87 0 pkt-line.c +100644 9df653f6f5afe720870658d7093bddbf3e66beaf 0 pkt-line.h +100644 738e36c1e81def4822ccc2a66bc2761402a07f26 0 ppc/sha1.c +100644 c3c51aa4d487f2e85c02b0257c1f0b57d6158d76 0 ppc/sha1.h +100644 f132696ee72bf4a2e3d608a24322a6839f9a03a8 0 ppc/sha1ppc.S +100644 33ef34a4119812674726254fee3f391fb5734fdb 0 pretty.c +100644 55a8687ad15788f8ea5a5beb463d216908f618b2 0 progress.c +100644 611e4c4d42d8d1164add09f926ad5b2ce088db5e 0 progress.h +100644 6a520855d6c418ecb1384ef9571b122b134af1af 0 quote.c +100644 c5eea6f18e2dfabd071b73e6507c34c2b7b5e39f 0 quote.h +100644 3b1c18ff9b9060d0dd437ce89aedb8871c66c54c 0 reachable.c +100644 40751810b64f8bbf9c0a633472a0ef27d23ed1a5 0 reachable.h +100644 2c03ec3069decb20f7557af4ac7dbe295f2dcf9c 0 read-cache.c +100644 d44c19e6b577023dcbaa188a0e67130ff4e5bd9a 0 receive-pack.c +100644 f751fdc8d832cae54647c1a70d888e979d324fd8 0 reflog-walk.c +100644 7ca1438f4d74b652f962c6bdfddd08fe0d75802d 0 reflog-walk.h +100644 39a3b23804d2da715c564459bf320be23d41c1bf 0 refs.c +100644 06ad26055661a9b9e475d0f8a7bd6d1cfb42e792 0 refs.h +100644 f61a3ab399aa6960fb8eba050321cea4a3b05344 0 remote.c +100644 091b1d041f8a4d255f59bfc001e098e692dbc15c 0 remote.h +100644 323e493dafee46c0d3f95e3c4cd9c4c9b463bbef 0 rerere.c +100644 f9b03862fe78b560ee606637be3b1ce972a2cc14 0 rerere.h +100644 3897fec53170c50921eb1952bc4bdf9c08480638 0 revision.c +100644 f64e8ce7ff999e9fe4a01205ae51775827484ed4 0 revision.h +100644 a3b28a64dc2d1b888b0ba2a135be10fe04651201 0 run-command.c +100644 5203a9ebb10b14bd06862abafed0ab73d7514a3d 0 run-command.h +100644 8ff1dc35390083c3648c4ee5790f35633d956069 0 send-pack.h +100644 c1c073b2f05a48772a45602cdc711eef6e211695 0 server-info.c +100644 6cf909463d4ad3681a2f35269db9dc944f4389c2 0 setup.c +100644 da357479cf19aad4bebc64f874c76fdf8566712b 0 sha1-lookup.c +100644 3249a81b3d664afc89c98e6d9dd6b512092a82f9 0 sha1-lookup.h +100644 e281c14f01d37ab7623998c2990914aca49a7a3b 0 sha1_file.c +100644 4fb77f8863ec075de38b84171d3ef039a00cee4c 0 sha1_name.c +100644 4d90eda19efe0a80c1cb39e8897ab3ed5e6fcf56 0 shallow.c +100644 6a48de05ff80f86050715ef3dab87a48b0a86ac9 0 shell.c +100644 bc02cc29ef0d5f640ab390614def995f30fe4691 0 shortlog.h +100644 45bb535773fd9c36f73502df9462f7de800009c8 0 show-index.c +100644 b6777812cb92c1c169ee395164d53a0c2e6eceb2 0 sideband.c +100644 a84b6917c7a17b5f8a922540801e98d46aa24431 0 sideband.h +100644 720737d856b694bc5239f0c18af372959c99e744 0 strbuf.c +100644 eba7ba423a2d3a383ef93f663c95695438269edf 0 strbuf.h +100644 ddd83c8c76112cecd5d23668aaca467601855a72 0 string-list.c +100644 4d6a7051fe5bccf04a0d0c32a90e5cf9c00dba3c 0 string-list.h +100644 5a5e781a15d7d9cb60797958433eca896b31ec85 0 symlinks.c +100644 1b97c5465b7d9e4404e11668b44c5c507a302d4a 0 t/.gitattributes +100644 b27e280083867ac03c4abc188f0f37291eb123a0 0 t/.gitignore +100644 0d65cedaa6566a6dd654753cb574c9ee64b1c90b 0 t/Makefile +100644 8f12d48fe8b4ffe4a4b37dcd16ce58e50837433f 0 t/README +100755 d5bab75d7da49ebb53e368d67f6b867f5417a125 0 t/aggregate-results.sh +100644 cacb273afff1fbddf152bb440451fa141589cf33 0 t/annotate-tests.sh +100644 4bddeb591ecc17ec532164d0d6cf1ad1a54eb996 0 t/diff-lib.sh +100644 a841df2a9e3a9ed64e81ab7b9778e59cfc714cad 0 t/lib-git-svn.sh +100644 dc473dfb53d5ffafee72738a55caf21732fa4fb1 0 t/lib-httpd.sh +100644 4717c2d33b70af6527f8951ec8a414e8caf87095 0 t/lib-httpd/apache.conf +100644 6dab2579cbf9658c3ac2bd55c8a66333d67eda47 0 t/lib-httpd/ssl.cnf +100644 168329adbc4edeedc98501575ccc9b9c81f0c061 0 t/lib-read-tree-m-3way.sh +100755 70df15cbd8339b552a56a95ca0c0893138550201 0 t/t0000-basic.sh +100755 620da5b32041b1ad69bfdcb6d139f2705386a5ff 0 t/t0001-init.sh +100755 4db4ac44c9611398db46dfbe2688c95e3b03605b 0 t/t0002-gitfile.sh +100755 3d8e06a20fade230136d50736a2f621d3c96002c 0 t/t0003-attributes.sh +100755 63e1217e7162435c3da8ec7984b5f6a53b3a10e2 0 t/t0004-unwritable.sh +100755 e45a9e40e432524454e94a041599b201a092879a 0 t/t0010-racy-git.sh +100755 1be7446d8d9f8a46b463f2474a8c25bdd33044d2 0 t/t0020-crlf.sh +100755 8fc39d77cec6168dae930beef785597dace24aa3 0 t/t0021-conversion.sh +100755 7d1ce2d0563b3734d754c171d21580075264bbb2 0 t/t0022-crlf-rename.sh +100755 6f8a4347d5397b8b396db800c12c6e045a0d2b7c 0 t/t0023-crlf-am.sh +100755 ccb0a3cb61be3bb591033564b221726a4cd3968d 0 t/t0030-stripspace.sh +100755 03dbe00102626e05c37e45d8a3b1364b99644942 0 t/t0040-parse-options.sh +100755 b177174ef53e7689cc8c18b134afdbe90be72744 0 t/t0050-filesystem.sh +100755 6e7501f352ee97636280357da54c50d73ceb0138 0 t/t0060-path-utils.sh +100755 807fb83af8c65304f1dae2ee35ba0f2909ddf465 0 t/t1000-read-tree-m-3way.sh +100755 4b44e131b27df0cc6a73590b045c2eb87b104f59 0 t/t1001-read-tree-m-2way.sh +100755 aa9dd580a658ffd980ec9689b01f7964580661f2 0 t/t1002-read-tree-m-u-2way.sh +100755 8c6d67edda1468ba0f1c7afc6d13ea3a7bbe0a90 0 t/t1003-read-tree-prefix.sh +100755 570d3729bd2312a8d9cf90f3d2e1121a58f43de6 0 t/t1004-read-tree-m-u-wf.sh +100755 b0d31f5a9bb8b3474665147327d94ad5067fa206 0 t/t1005-read-tree-reset.sh +100755 d8b7f2ffbcc0427b1ae9d48feb4387f580e81d61 0 t/t1006-cat-file.sh +100755 1ec0535138c72bbd1e497c35c21bc5ea46b0315f 0 t/t1007-hash-object.sh +100755 fc386ba033ac165a5f4a9fca0c6c6f5db49a314e 0 t/t1020-subdirectory.sh +100755 7f7fc36734d96de96801c59af41024db97dc614d 0 t/t1100-commit-tree-options.sh +100755 09a8199335cbdf96f8aba75d47a321f0cfb828d9 0 t/t1200-tutorial.sh +100755 64567fb94d5c3f9587b643333212cdb37a4661ac 0 t/t1300-repo-config.sh +100755 dc85e8b60a5c10e57047d1692e383f177e2c478d 0 t/t1301-shared-repo.sh +100755 8d305b43725f8cf60e7ee802df1923feb98eeae5 0 t/t1302-repo-version.sh +100755 f98f4c51796e6f7a7181568a134e21ecd9dc2c4f 0 t/t1303-wacky-config.sh +100755 b31e4b1ac66e56d67ba48ab213c4ef9c32f05ea8 0 t/t1400-update-ref.sh +100755 73f830db2374e751fb46e25b345e860979b9dd05 0 t/t1410-reflog.sh +100755 dc9e402c55574d981e161d4e38e74617c411f46d 0 t/t1420-lost-found.sh +100755 85da4caa7ed1b8bcaca7b21e218f2d1839d2db82 0 t/t1500-rev-parse.sh +100755 2ee88d8a069288d0d9f6931231162e04d6b0917a 0 t/t1501-worktree.sh +100755 997002d4c40dd8e66e3be5a701e3d99bab1c57c4 0 t/t1502-rev-parse-parseopt.sh +100755 95244c9bcf54de8cb3584b4022e53a84051d496f 0 t/t1503-rev-parse-verify.sh +100755 91b704a3a4ce6771071d19bd84aa228856fe6875 0 t/t1504-ceiling-dirs.sh +100755 f7e1a735ec8699616280a086f59dc50c078bfaa7 0 t/t2000-checkout-cache-clash.sh +100755 ef007532b15108d72445f7c95a2906a3039fbbbb 0 t/t2001-checkout-cache-clash.sh +100755 70361c806e1baf1b26810983374c53eb49ea2f2d 0 t/t2002-checkout-cache-u.sh +100755 71894b37439bd1b9c72194cbbabe37680d2f9743 0 t/t2003-checkout-cache-mkdir.sh +100755 39133b8c7a4b56cb7273cec607ea89081a426eff 0 t/t2004-checkout-cache-temp.sh +100755 a84c5a6af9e69ffec7689827ce1ba653a658a73f 0 t/t2005-checkout-index-symlinks.sh +100755 0526fce163fc13273daf035a0920a6b53a3acefb 0 t/t2007-checkout-symlink.sh +100755 3e098ab31e1944abe8e5815c0f219947620b6618 0 t/t2008-checkout-subdir.sh +100755 f3c21520877e271506be13b5cabad5e7906f0c91 0 t/t2009-checkout-statinfo.sh +100755 7cc0a3582ef1bb1598bc5dfc5334bfbe006c3f5a 0 t/t2010-checkout-ambiguous.sh +100755 88f268b9d7a696a06f5ce560c1e8ed0f3d8dc3a3 0 t/t2050-git-dir-relative.sh +100755 6ef2dcfd8afece86aaf6345630179af179eb2ed9 0 t/t2100-update-cache-badpath.sh +100755 59b560bfdf240e87516aadd6a31a2fe84e85d49a 0 t/t2101-update-index-reupdate.sh +100755 19d0894d260787d37a43199d7a3f6c3aa37d32aa 0 t/t2102-update-index-symlinks.sh +100755 332694e7d38083fad18b3e53e4def268d54e9423 0 t/t2103-update-index-ignore-missing.sh +100755 f57a6e077c3b85dcdedc3f4813150feebc8e647d 0 t/t2200-add-update.sh +100755 d24c7d9e5fce0e9c0f8ef5576dab86ffdbc11331 0 t/t2201-add-update-typechange.sh +100755 6a8151064c8256bd03a90460eb1bd6970428f2f0 0 t/t2202-add-addremove.sh +100755 bc0a3513920cab41e4335b8c1b5163e25e8354d3 0 t/t3000-ls-files-others.sh +100755 1caeacafa7ae70506e626498d274dbfa25f1b036 0 t/t3001-ls-files-others-exclude.sh +100755 8704b04e1b4150357a7a01c91ac59bb1f22cbb8e 0 t/t3002-ls-files-dashpath.sh +100755 ec1404063701eef04667d5ffbbb4bdc8051c773b 0 t/t3010-ls-files-killed-modified.sh +100755 af8c4121abfc28b7e289b39936df45bd5b82cf22 0 t/t3020-ls-files-error-unmatch.sh +100755 aff360303ae2a304bff4799def6906defdb85843 0 t/t3030-merge-recursive.sh +100755 f6973e96a59916c6048222bfa0064aec5dea3746 0 t/t3040-subprojects-basic.sh +100755 4261e9641e00fb3b543384b6a8dbbcc1a214b598 0 t/t3050-subprojects-fetch.sh +100755 3ce501bb9794900b99fbbf2f2ccfbb330f7947a7 0 t/t3060-ls-files-with-tree.sh +100755 6e6a2542a22712006ae23874069c55943a3cba27 0 t/t3100-ls-tree-restrict.sh +100755 4dd7d12bac62eae23516686c0ece0edf037f0317 0 t/t3101-ls-tree-dirname.sh +100755 7a83fbfe4f6b47dc5dbaa3df59c8633ae5c2c8f8 0 t/t3200-branch.sh +100755 f86f4bc5ebcc0e36ddb4071a6aeb855e1039faa6 0 t/t3201-branch-contains.sh +100755 7fe4a6ecb05df0fbfb825fbb08207f7672e1775f 0 t/t3202-show-branch-octopus.sh +100755 c2dec1c6320a0f9b555e3cd38d164c4e3efcb51e 0 t/t3210-pack-refs.sh +100755 0574ef1f101df172a30755726b0ea1b6c2ef5f7d 0 t/t3300-funny-names.sh +100755 91bb5e1d9eea0b2f1ff7a1120d97ca2876a8f277 0 t/t3400-rebase.sh +100755 166ddb1447db4c33a79f0e9aea21cb00e8a151f2 0 t/t3401-rebase-partial.sh +100755 7b7d07269ae35f56dd02a223f350ae0da97bae85 0 t/t3402-rebase-merge.sh +100755 0d33c71daa557e68268dfb2279a02fe2afca1ed7 0 t/t3403-rebase-skip.sh +100755 ffe3dd97b7b1c056d854e28795e1313ce1633452 0 t/t3404-rebase-interactive.sh +100755 e5ad67c643ffee9b79fce813673732faa950714f 0 t/t3405-rebase-malformed.sh +100755 539108094345e3e0ba4cf03fc20a5ca6486fa230 0 t/t3406-rebase-message.sh +100755 4de550a632e6ead08c9629e80901e4735c53f55c 0 t/t3407-rebase-abort.sh +100755 e12cd578e8663ad8717aecde310bb0b6a500c2a2 0 t/t3408-rebase-multi-line.sh +100755 4911c48378a137471d2ad56747ceed11d0115be5 0 t/t3500-cherry.sh +100755 6da212825a447866364979c2fb10778b6bbc02d5 0 t/t3501-revert-cherry-pick.sh +100755 0ab52da902c8d602e9c4d64660aa4a7e8e35544f 0 t/t3502-cherry-pick-merge.sh +100755 b0faa299183df5fe06ccaf383bce47cbb9a0cf89 0 t/t3503-cherry-pick-root.sh +100755 79c06adf1f035cf727771974b2f9713da9d2fb8c 0 t/t3600-rm.sh +100755 7d123d17fc156c61a8e85a399c3762e8075485de 0 t/t3700-add.sh +100755 e95663d8e66d5b94e574a6b956625fccfd341a05 0 t/t3701-add-interactive.sh +100755 c851db8ca9373a890ede7c230710690d5b4b4165 0 t/t3800-mktag.sh +100755 883281dbd6c02ea7b2d90336c2629eafacee0257 0 t/t3900-i18n-commit.sh +100644 ee31e1973829739baebbdc1ba0a56391adb2fe2f 0 t/t3900/1-UTF-8.txt +100644 63f4f8f121dffe43c1902d1a7357b6303072714d 0 t/t3900/2-UTF-8.txt +100644 546f2aac01b67e39d19de601f5586097b34a8325 0 t/t3900/EUCJP.txt +100644 74b533042f6d1dfd5cb8fae5a538a43938c2df8d 0 t/t3900/ISO-2022-JP.txt +100644 7cbef0ee6f430c611134a06a6dd6c12fbea589d5 0 t/t3900/ISO-8859-1.txt +100755 38c21a6a7fa9a643030f68fd6dd1269b9c640608 0 t/t3901-8859-1.txt +100755 235f372832cb32aefff0a00c4f2ac0e19de2e55d 0 t/t3901-i18n-patch.sh +100755 5f5205cd022ba3a96eb8e5eb73292bd0d4a8ab87 0 t/t3901-utf8.txt +100755 fe4fb5116ac4c482c357f0af3f0a34da27cee237 0 t/t3902-quoted.sh +100755 8d4804b65818f7fc55f0c0736fde673ac7512a8a 0 t/t3903-stash.sh +100755 c44b27aeb24816a346f0aa84d70546a0ffd83b2a 0 t/t4000-diff-format.sh +100755 a32692417db73444dbdc143e6908b7371be79d42 0 t/t4001-diff-rename.sh +100755 a4cfde6b2927a4655f582d7e92dad4568f7b5f89 0 t/t4002-diff-basic.sh +100755 8b1f875286b25b5fc1a527b5b0281f866724a82d 0 t/t4003-diff-rename-1.sh +100755 3d25be7a6709cdd23e0d583a8f1a3e19a3927cd8 0 t/t4004-diff-rename-symlink.sh +100755 66300173124eee8480e7214322faf8bc493ad940 0 t/t4005-diff-rename-2.sh +100755 4e92fce1d00a55cfbc39e55b53f95cc309e96ff2 0 t/t4006-diff-mode.sh +100755 104a4e1492b65a64d061e1ce1df0b0a2a49fb20e 0 t/t4007-rename-3.sh +100755 26c2e4aa65c539c527ae8e2d71b5abb40c21fbbd 0 t/t4008-diff-break-rewrite.sh +100755 d2b45e7b8fb3902cb740e0df582f92195a295f24 0 t/t4009-diff-rename-4.sh +100755 ad3d9e48454d2e72afce682df009cdaaee9ba3c5 0 t/t4010-diff-pathspec.sh +100755 c6d13693ba45b594704c2ef55d40540ee3f9c25f 0 t/t4011-diff-symlink.sh +100755 eced1f30fb8475739aef52230bbb79946a0f76d8 0 t/t4012-diff-binary.sh +100755 9337b81064bbdbe4e7f590830b458c48226c4a17 0 t/t4013-diff-various.sh +100644 78f8970e2be272114368ee28ca2d55f345a6494a 0 t/t4013/diff.config_format.subjectprefix_DIFFERENT_PREFIX +100644 3a9f78a09df1bb12aefbb2b5fb75fd0b9455ed06 0 t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_master +100644 a61ad8cb130093ed63bba5335a0b283f9136177e 0 t/t4013/diff.diff-tree_--cc_--patch-with-stat_--summary_side +100644 49f23b9215c3179d8923915ee2e497ccbc53875a 0 t/t4013/diff.diff-tree_--cc_--patch-with-stat_master +100644 cc6eb3b3d50f2d620df7116bf64992a4dd42b736 0 t/t4013/diff.diff-tree_--cc_--stat_--summary_master +100644 50362be7bf851759885ad17847f5913e5dfb0b3c 0 t/t4013/diff.diff-tree_--cc_--stat_--summary_side +100644 fae7f33255faef186aa7b987c29adfe33d0365f1 0 t/t4013/diff.diff-tree_--cc_--stat_master +100644 5ecb4e14ae4718bb26dbe5657abd5660dc45914e 0 t/t4013/diff.diff-tree_--cc_master +100644 fc177ab3f24c7eddc71d76b803515f7314d9b136 0 t/t4013/diff.diff-tree_--patch-with-raw_initial +100644 bd905b1c573056f92d7f3b11ad4898d19fd0e5f8 0 t/t4013/diff.diff-tree_--patch-with-stat_initial +100644 7bb8b45e3eea89b4015cabb70d34483352b40f1a 0 t/t4013/diff.diff-tree_--pretty=oneline_--patch-with-raw_initial +100644 cbdde4f400fe96ea565dcf28b6b474c2218b6104 0 t/t4013/diff.diff-tree_--pretty=oneline_--patch-with-stat_initial +100644 cd79f1a0ff23a4399416d85737f312e0b8652a38 0 t/t4013/diff.diff-tree_--pretty=oneline_--root_--patch-with-raw_initial +100644 d5c333a378c9407abf179d6cc3f5233ca8b5f116 0 t/t4013/diff.diff-tree_--pretty=oneline_--root_--patch-with-stat_initial +100644 3c5092c6993f454dc7392a3846b93cc3fa47f7e5 0 t/t4013/diff.diff-tree_--pretty=oneline_--root_-p_initial +100644 08920ac658b07ed83376bdd492020f7787ad1ad5 0 t/t4013/diff.diff-tree_--pretty=oneline_--root_initial +100644 94b76bfef159a0d8e2d7c48194110cb8465545c2 0 t/t4013/diff.diff-tree_--pretty=oneline_-p_initial +100644 d50970d5745278a47da683aa20968a912493cb01 0 t/t4013/diff.diff-tree_--pretty=oneline_initial +100644 3a85316d8a3416e2f280e01d02e0bf3cfce13cc8 0 t/t4013/diff.diff-tree_--pretty_--patch-with-raw_initial +100644 2e08239a46a72e76aad4e38940cdc822ca511793 0 t/t4013/diff.diff-tree_--pretty_--patch-with-stat_initial +100644 4d30e7eddc05aa561b43519b031f89de8a30fd98 0 t/t4013/diff.diff-tree_--pretty_--patch-with-stat_side +100644 a3203bd19bd74f1308ca75396fd9a3cfa2064ac8 0 t/t4013/diff.diff-tree_--pretty_--root_--patch-with-raw_initial +100644 7dfa6af3c97b1ad469135bb9a9f3c1f8d6d4b60d 0 t/t4013/diff.diff-tree_--pretty_--root_--patch-with-stat_initial +100644 43bfce253eaf451e2f0a19042c9cbb8879c6fef1 0 t/t4013/diff.diff-tree_--pretty_--root_--stat_--summary_initial +100644 9154aa4d4700b4a7c1e4c205f43694c198419ece 0 t/t4013/diff.diff-tree_--pretty_--root_--stat_initial +100644 ccdaafb3772e80571441f5e4263f79a7b8bc5bfd 0 t/t4013/diff.diff-tree_--pretty_--root_--summary_-r_initial +100644 58e5f74aeae880c69776bfa8e51bc9e5fe2463cf 0 t/t4013/diff.diff-tree_--pretty_--root_--summary_initial +100644 d0411f64ec5d104de3169da28453276880984917 0 t/t4013/diff.diff-tree_--pretty_--root_-p_initial +100644 94e32eabb1f82eb614f1a92d64d0ada403a4df16 0 t/t4013/diff.diff-tree_--pretty_--root_initial +100644 c22983ac4ae05a8c8d9591c91c0a44bc8f42052e 0 t/t4013/diff.diff-tree_--pretty_--stat_--summary_initial +100644 8fdcfb4c0ad64bc92ddda2418cb5f7fa1138cc7a 0 t/t4013/diff.diff-tree_--pretty_--stat_initial +100644 9bc2c4fbad20e42dd0a007eaa23c5994eb675588 0 t/t4013/diff.diff-tree_--pretty_--summary_initial +100644 3c9942faf4b1a1e6d8c3bbbab5627a32ecc96be6 0 t/t4013/diff.diff-tree_--pretty_-p_initial +100644 b993aa7b893c9ba6c276045a400fe0a849265fcd 0 t/t4013/diff.diff-tree_--pretty_-p_side +100644 14715bf7d08ff7696da2572544092f6be9029e18 0 t/t4013/diff.diff-tree_--pretty_initial +100644 e9b6e1c10211c4fb60262a55a28a503c3ed9bb47 0 t/t4013/diff.diff-tree_--pretty_side +100644 5aa84b2a866262a4e6b2d496e7da5b6f7b9d4d88 0 t/t4013/diff.diff-tree_--root_--abbrev_initial +100644 d295e475ddc626dc18381a5c5e839d3b2c26126e 0 t/t4013/diff.diff-tree_--root_--patch-with-raw_initial +100644 1562b627085ff0ad8ffaea61cc42ed39a9d94fc8 0 t/t4013/diff.diff-tree_--root_--patch-with-stat_initial +100644 3219c72fcbce5ee5a1c2b4e7bb87e696db9a0b18 0 t/t4013/diff.diff-tree_--root_-p_initial +100644 0c5361688c54c2599e240252ce70e4fad457c924 0 t/t4013/diff.diff-tree_--root_-r_--abbrev=4_initial +100644 c7b460faf6900e5730a08ab3b90bfda8e1ad48ca 0 t/t4013/diff.diff-tree_--root_-r_--abbrev_initial +100644 eed435e175d40a3f6fa8b3a0a534936360e86eea 0 t/t4013/diff.diff-tree_--root_-r_initial +100644 ddf6b068abe26f7516c37ceb1dca1d646343a499 0 t/t4013/diff.diff-tree_--root_initial +100644 b8e4aa2530717abc7d7b8bea62f12d615dfcd759 0 t/t4013/diff.diff-tree_-c_--abbrev_master +100644 ac9f641fb48272f25f3385dfbbd05777564f16bc 0 t/t4013/diff.diff-tree_-c_--stat_--summary_master +100644 2afcca11f4d0c4db6e259e85540e4d90f8c76a10 0 t/t4013/diff.diff-tree_-c_--stat_--summary_side +100644 c2fe6a98c5f9818f9caf5e0a314aee45831e5329 0 t/t4013/diff.diff-tree_-c_--stat_master +100644 e2d2bb26114ac886fbf2467dc7a33ec8cfab5daf 0 t/t4013/diff.diff-tree_-c_master +100644 b60bea039d991675e9d1480d5089c416aa68200a 0 t/t4013/diff.diff-tree_-p_-m_master +100644 e20ce883702ae0e430350ae849b28c44a081487f 0 t/t4013/diff.diff-tree_-p_initial +100644 b182875fb2fb3f0020dbf49fd9fd4c80e016e651 0 t/t4013/diff.diff-tree_-p_master +100644 c5a3aa5aa4688cb354bff44cbb6062ef8259a617 0 t/t4013/diff.diff-tree_-r_--abbrev=4_initial +100644 0b689b773c67dfef103d95c55ee9958f1b8f32ed 0 t/t4013/diff.diff-tree_-r_--abbrev_initial +100644 1765d83ce461d78e59cf7d505ec63331a7c70577 0 t/t4013/diff.diff-tree_-r_initial +100644 b49fc5345730346ab50142f55a1b8796545ead89 0 t/t4013/diff.diff-tree_initial +100644 fe9226f8a12323c3a36ff2c0c2fd9938852575c8 0 t/t4013/diff.diff-tree_master +100644 a88e66f81774579c511b6cd546f5043a9c975c36 0 t/t4013/diff.diff_--abbrev_initial..side +100644 d0d96aaa91eec7f5a6cb833e43bbd3ec89efad98 0 t/t4013/diff.diff_--name-status_dir2_dir +100644 6a47584777a65cff71e2ebfac8c8f12944de17bb 0 t/t4013/diff.diff_--no-index_--name-status_dir2_dir +100644 3590dc79a65af7299c4b56fd632489920b62f0fb 0 t/t4013/diff.diff_--patch-with-raw_-r_initial..side +100644 b21d5dc6f391fe977cc7c9b9c6badf9b261c5f72 0 t/t4013/diff.diff_--patch-with-raw_initial..side +100644 9ed317a198c71fcec0b44a7b736b61550eed9394 0 t/t4013/diff.diff_--patch-with-stat_-r_initial..side +100644 8b50629e668a385179eb9586b472c51237492232 0 t/t4013/diff.diff_--patch-with-stat_initial..side +100644 0517b5d63129f44275b16f51819d9c08bf37d430 0 t/t4013/diff.diff_--stat_initial..side +100644 245220d3f9b475b4281395639d85122feaa7948d 0 t/t4013/diff.diff_-r_--stat_initial..side +100644 5bb2fe2f280d2def45c113b5c0daa7ab789faed0 0 t/t4013/diff.diff_-r_initial..side +100644 c8adaf595863e66fb9ae061cd20176c981316372 0 t/t4013/diff.diff_initial..side +100644 43346b9ba443fe22b56f0874a7cc885461d2aa81 0 t/t4013/diff.format-patch_--attach_--stdout_initial..master +100644 d7490a9fd729890c80a4b8fc3da0783997f81a04 0 t/t4013/diff.format-patch_--attach_--stdout_initial..master^ +100644 38f790290a41311e490c493bdaf71774853cc861 0 t/t4013/diff.format-patch_--attach_--stdout_initial..side +100644 fca5cce373767d96fd68a17a196889c8c9ea172f 0 t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master +100644 6d6fac390849c964e75b56e48808a78dd3428ce1 0 t/t4013/diff.format-patch_--inline_--stdout_initial..master +100644 18a1110def4bbe25c0bd7020d35df589ef6f528f 0 t/t4013/diff.format-patch_--inline_--stdout_initial..master^ +100644 4f258b8858df79ecf475514b69df904e83e29ffa 0 t/t4013/diff.format-patch_--inline_--stdout_initial..master^^ +100644 e86dce69a3a78d5cfe5cd82067df0381b0f635bd 0 t/t4013/diff.format-patch_--inline_--stdout_initial..side +100644 8dab4bf93ebd5f3e5ee6e899890d6e53af9ab967 0 t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ +100644 8b88ca49276695aead0083ad49c53715874e11dc 0 t/t4013/diff.format-patch_--stdout_initial..master +100644 47a4b88637d4dc8650f0480278ae67b907eb5080 0 t/t4013/diff.format-patch_--stdout_initial..master^ +100644 e7650884755b993661ef14fbc812edd7c2710702 0 t/t4013/diff.format-patch_--stdout_initial..side +100644 3ceb8e73c5d4e963a2c08eddf41197431ad52178 0 t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_ +100644 43d77761f988b7f43f46a5d8fd85d406ef9caa42 0 t/t4013/diff.log_--patch-with-stat_master +100644 5187a26816723476b049c44bb3801816e8672cdf 0 t/t4013/diff.log_--patch-with-stat_master_--_dir_ +100644 c9640976a8f3bd724004f945b00d71f43f00c8ea 0 t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master +100644 ad050af55ffa6983bc08ef5919dd20229584fd59 0 t/t4013/diff.log_--root_--patch-with-stat_--summary_master +100644 628c6c03bc6195268c3e7e003478fd042ef36db2 0 t/t4013/diff.log_--root_--patch-with-stat_master +100644 5d4e0f13b59652b170c0b2498319f970d071f774 0 t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master +100644 217a2eb203718b91ccd79a04e0369f4c5d2905aa 0 t/t4013/diff.log_--root_-p_master +100644 e17ccfc2340eadbb8c393de0aa2793f6f899ba56 0 t/t4013/diff.log_--root_master +100644 5e3243897294d1fc4193c1de3fec95ce58ab22af 0 t/t4013/diff.log_-SF_-p_master +100644 c1599f2f520090b0717d951a69ade5a9960f8038 0 t/t4013/diff.log_-SF_master +100644 f8fefef2c3869337ba048d5cd6d95169dc60cf00 0 t/t4013/diff.log_-p_master +100644 e9d9e7b40a08d803901ac001e4bf9f0c37ad8b15 0 t/t4013/diff.log_master +100644 221b46a7cc2c174ec605cab02211de97bf84f010 0 t/t4013/diff.show_--patch-with-raw_side +100644 377f2b7b7a34a1e83bbcf0f2d72981685a678c24 0 t/t4013/diff.show_--patch-with-stat_--summary_side +100644 fb14c530d2dd79730320badff2547ebf755bd350 0 t/t4013/diff.show_--patch-with-stat_side +100644 8c89136c4d84091da3f2b9f6eaf582112f90a9ba 0 t/t4013/diff.show_--root_initial +100644 5bd597762869443cfd91ba8ebdaeff6e8b515ebf 0 t/t4013/diff.show_--stat_--summary_side +100644 3b22327e48122187af12db46027f17526c8e9caf 0 t/t4013/diff.show_--stat_side +100644 4c4066ae48e213b6a0f4bf51226c4322ac9348c4 0 t/t4013/diff.show_initial +100644 9e6e1f27105ca50262e5eb3f2ff132b46c12fe45 0 t/t4013/diff.show_master +100644 530a073b19d75e37aeb241200b6b306e6803ca09 0 t/t4013/diff.show_side +100644 6a467cccc190449049b7baed2dde0a2f9027e663 0 t/t4013/diff.whatchanged_--patch-with-stat_--summary_master_--_dir_ +100644 1e1bbe19638eeeffb4e656239545ea91fda3dbf2 0 t/t4013/diff.whatchanged_--patch-with-stat_master +100644 13789f169b6803ba71680470197c50b51795e4e4 0 t/t4013/diff.whatchanged_--patch-with-stat_master_--_dir_ +100644 5facf2543db0135ceb3aafdc5ffe0395b429f44c 0 t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master +100644 02911535870b012720c9ae095499c61e6e242c7e 0 t/t4013/diff.whatchanged_--root_--patch-with-stat_--summary_master +100644 9b0349cd555eb17836b8c056d7aae8cf1eabe537 0 t/t4013/diff.whatchanged_--root_--patch-with-stat_master +100644 10f6767e498b0d5bdadd73067e4ef2dd5f028e89 0 t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master +100644 ebf1f0661e07bd06098d1609a5a22b8e9bbae436 0 t/t4013/diff.whatchanged_--root_-p_master +100644 a405cb6138857d3a1bc1b590aa55565c34770e4c 0 t/t4013/diff.whatchanged_--root_master +100644 f39da84822845984e0f431cf84e71fc48853a3c8 0 t/t4013/diff.whatchanged_-SF_-p_master +100644 0499321d0ebf57fc2d583afd46ce7813a1ba9fde 0 t/t4013/diff.whatchanged_-SF_master +100644 f18d43209c0a90d2d6fd474e849690f1b2120cfe 0 t/t4013/diff.whatchanged_-p_master +100644 cd3bcc2c7269c17ff25504c8b7ca02b55ba8d8ed 0 t/t4013/diff.whatchanged_master +100755 7fe853c20d1111e40371a3796d82bb8485f5ebcf 0 t/t4014-format-patch.sh +100755 a27fccc8dce431169ce41f7137fb75f44149719c 0 t/t4015-diff-whitespace.sh +100755 f07035ab7ec72557be7a0cba9ea286bcbaa79da9 0 t/t4016-diff-quote.sh +100755 60dd2014d5ae5d5e9e168b8b60278d90ef93cc53 0 t/t4017-diff-retval.sh +100755 e747e842272df5935f863f78ccfc3b311f64228b 0 t/t4017-quiet.sh +100755 833d6cbcfc063f336d97689ae4e547cf5e956b69 0 t/t4018-diff-funcname.sh +100755 0d9cbb62615c0d94da784f63907989988b0e8151 0 t/t4019-diff-wserror.sh +100755 637b4e19d52e81cf1472a4ed9dcfb0c9ff00da2b 0 t/t4020-diff-external.sh +100644 db2f89090c1c4de05e4f82ea39ea118fccfd48dd 0 t/t4020/diff.NUL +100755 43d64bbd826049c5a0a8cf365ed5cb3f4489752c 0 t/t4021-format-patch-numbered.sh +100755 ba43f185494630c50fc2a168df8dcd45c0b2421b 0 t/t4021-format-patch-signer-mime.sh +100755 bf996fc414d3b5ea16488a8b274973a8347b29cb 0 t/t4022-diff-rewrite.sh +100755 4dbfc6e8b751a6c93b1f9dfee8ce649235c98c93 0 t/t4023-diff-rename-typechange.sh +100755 c4d733f5db6a4d390762505b770954cdbf6cc82f 0 t/t4024-diff-optimize-common.sh +100755 7a3dbc1ea22fd19a54da8949abc368c112377b19 0 t/t4025-hunk-header.sh +100755 b61e5169f4e9e8d9f87b9ea16e71dfcf1fb9f340 0 t/t4026-color.sh +100755 ba6679c6e4032bb12e4206226f95770946ece8cc 0 t/t4027-diff-submodule.sh +100755 204ba673cb58f905f2f35ff5b83294b2a2943f48 0 t/t4028-format-patch-mime-headers.sh +100755 e0c67740a5ef85aaa3edc9a4514da72c92ce30ec 0 t/t4100-apply-stat.sh +100644 540e64db85575e10fe02b3b85af6a353999aab8f 0 t/t4100/t-apply-1.expect +100644 90ab54f0f586c87ace077be87fba396c8f2781a0 0 t/t4100/t-apply-1.patch +100644 d1e6459749fe3dd4635c9136ccbb148a85405676 0 t/t4100/t-apply-2.expect +100644 f5c7d601fc955b15d15012a5b49df83364b6ebf4 0 t/t4100/t-apply-2.patch +100644 912a552a7a67be283c963da84bf044fa779fe633 0 t/t4100/t-apply-3.expect +100644 90cdbaa5bb62a4adfa0a5eb3f6fe223b9de50e7b 0 t/t4100/t-apply-3.patch +100644 1ec028b3d05d79fab29d2ac61027c8ef00bf8401 0 t/t4100/t-apply-4.expect +100644 4a56ab5cf416e2144b5dbdbe06d10b51255fabe2 0 t/t4100/t-apply-4.patch +100644 b387df15d4fc81574ac18f41174205c86a6ad503 0 t/t4100/t-apply-5.expect +100644 5f6ddc105950eac20bcacb7987fabff015320ee3 0 t/t4100/t-apply-5.patch +100644 1c343d459eb8fc129f53d3a13f0e0ffa79058139 0 t/t4100/t-apply-6.expect +100644 a72729a712038dc64f55d154eb9a94713e3269c9 0 t/t4100/t-apply-6.patch +100644 1283164d9909164c2428bc1a08cbff1d819fe71c 0 t/t4100/t-apply-7.expect +100644 07c6589e74fa5afbff9bdfd8d7b7b41f873a005b 0 t/t4100/t-apply-7.patch +100644 eef7f2e65cc3e2192935b9bde3ea001244b6db18 0 t/t4100/t-apply-8.expect +100644 5ca13e65949047b5a1e8170422519cd69418da88 0 t/t4100/t-apply-8.patch +100644 eef7f2e65cc3e2192935b9bde3ea001244b6db18 0 t/t4100/t-apply-9.expect +100644 875d57d567e2b807587a7e4d2a9bb7771225e28c 0 t/t4100/t-apply-9.patch +100755 da8abcf36418dbd2e9d8ec85871c245991f96fda 0 t/t4101-apply-nonl.sh +100644 1010a88f4727373c20e7ab46515a79b9d3cd9675 0 t/t4101/diff.0-1 +100644 36460a243ac10e256df270cec10a7498a0a32009 0 t/t4101/diff.0-2 +100644 b281c43e5b5f4926e365cbd168e43420e8a4a63a 0 t/t4101/diff.0-3 +100644 f0a2e92770870d78d32bef7fe2ed63ad4ca105de 0 t/t4101/diff.1-0 +100644 2a440a5ce2adae53a23363a3cd3b6af5ff915fd0 0 t/t4101/diff.1-2 +100644 61aff975b69f49430ccb0b6ebd14193fd9f78b4e 0 t/t4101/diff.1-3 +100644 c2e71ee344d032ee38bf325099ed6386f180f287 0 t/t4101/diff.2-0 +100644 a66d9fd3a137b22edf5a49cf811970fc85c985d9 0 t/t4101/diff.2-1 +100644 5633c831de0bb2d5d482acc89c32bb3cb39d02d7 0 t/t4101/diff.2-3 +100644 10b1a41edf734aef77feb4a5f40cf358a8f48db3 0 t/t4101/diff.3-0 +100644 c799c60fb9d460ff24160082a9c1346c0751dd4b 0 t/t4101/diff.3-1 +100644 f8d1ba6dc24669ff19530e0d3ed064db2c8e7077 0 t/t4101/diff.3-2 +100755 d42abff1ad59343fa1c84bded9a82c3212370da0 0 t/t4102-apply-rename.sh +100755 7da0b4bb8bfa96765b9f6eaa1693e2e24e82d335 0 t/t4103-apply-binary.sh +100755 e7e2913de745cc9f7639103757933f6238fdd564 0 t/t4104-apply-boundary.sh +100755 3266e394003958b62509b7bfe6652abd03fdfcb7 0 t/t4105-apply-fuzz.sh +100755 ac58083fe224100987800e9b5ee3e388d9b4d97c 0 t/t4109-apply-multifrag.sh +100644 1db5ff10501497459d07e227f02764576a6010e1 0 t/t4109/expect-1 +100644 bc52924112ba857784169157d3f650eb35a931ba 0 t/t4109/expect-2 +100644 cd2a475feb2280a24b05870c00f0693dd1de605b 0 t/t4109/expect-3 +100644 1d411fc3ccebe33ee086cfeaea7cea1beb041a6a 0 t/t4109/patch1.patch +100644 8c6b06d536a06d786f0752ec2aa2a7fae22c8926 0 t/t4109/patch2.patch +100644 d696c55a752ac61b86691192d46da2566e299830 0 t/t4109/patch3.patch +100644 4b085909b160a093e9edb4f91de87f7637f218b5 0 t/t4109/patch4.patch +100755 09f58112e0229a41ea2a5d2ea6e8c23d2523298d 0 t/t4110-apply-scan.sh +100644 87cc493ec8a0f6f042a5df3c302433c6e45d8dd3 0 t/t4110/expect +100644 56139080dc157ab9e58dec01399c40362a9e710e 0 t/t4110/patch1.patch +100644 04974247ec927d9b25bf0b30c372e4669321dad0 0 t/t4110/patch2.patch +100644 26bd4427f808bb1d0929833f2784e384e35ab961 0 t/t4110/patch3.patch +100644 9ffb9c2d7ecd2e87eb3a92ed99be90beba3e16be 0 t/t4110/patch4.patch +100644 c5ac6914f987d3a41266dca735d872e4c131d02c 0 t/t4110/patch5.patch +100755 f9ad183758c28ff648890d1bd4bbd599562cd795 0 t/t4112-apply-renames.sh +100755 66fa51591eb7ee8f102fd86e30e54af2da3ea310 0 t/t4113-apply-ending.sh +100755 55334927abb33864a55f8ff49fd0c0c94a3c1769 0 t/t4114-apply-typechange.sh +100755 9ace578f17a07aafc050ccaf935aef8a4a3cab4e 0 t/t4115-apply-symlink.sh +100755 2298ece8019d79ef718ef658bdac74493d265b92 0 t/t4116-apply-reverse.sh +100755 e9ccd161ee96a5bdbb4bf77de406ea51dacfb5de 0 t/t4117-apply-reject.sh +100755 f92e259cc6f251ec6f89edee3fc16720f264d82f 0 t/t4118-apply-empty-context.sh +100755 3c73a783a7e908070308fb1f972f6b5d152e12a4 0 t/t4119-apply-config.sh +100755 83d4ba679850c2ae2548bcfcce3f22227fcde8c7 0 t/t4120-apply-popt.sh +100755 aff551a1d787477eb2db34d96217f66ca03c435d 0 t/t4121-apply-diffs.sh +100755 841773f75fc085d07836b39b3775f49bde5d8d19 0 t/t4122-apply-symlink-inside.sh +100755 984157f03b9744aa491c888fab9e6aef95dfdc6b 0 t/t4123-apply-shrink.sh +100755 85f3da2b98a881647837323e3af0378ce59a9db5 0 t/t4124-apply-ws-rule.sh +100755 3b471b641ba2d3274dd1ab89948485ff8ce4dfdb 0 t/t4125-apply-ws-fuzz.sh +100755 ceb6a79fe0c8ca5b26a9e148215556f2aa344eb9 0 t/t4126-apply-empty.sh +100755 1f859dd908cb298bf89a13822e8fc19882060689 0 t/t4127-apply-same-fn.sh +100755 2dd0c75f964b690977e40a3a8235cc324dc6826e 0 t/t4128-apply-root.sh +100755 6e6aaf59364456e21bb1deda960edb5295a71131 0 t/t4150-am.sh +100755 7d86cdff64522f588a3d3e781cf2b272087cfd88 0 t/t4151-am-abort.sh +100755 b68ab11f2915789cd04ac6bd43363aeab2079198 0 t/t4200-rerere.sh +100755 405b97119175a1c0fa75a9db30c6b1ab076cc44e 0 t/t4201-shortlog.sh +100755 4c8af45f834d034529c2a627768a0a3e6f1aac8d 0 t/t4202-log.sh +100755 e395ff4e341bacea21cc5cd909304b7bb4fcb044 0 t/t5000-tar-tree.sh +100755 e9f3e72c7ee5584d956d46126956c641a7d53905 0 t/t5100-mailinfo.sh +100644 f5892c9da73eae30d7629ca5e7f0ba6a4c621c73 0 t/t5100/0010 +100644 8c052777e0d216e84bb8464b1ceaff1bc7721154 0 t/t5100/info0001 +100644 49bb0fec85323c8c19182bc2a50ceb64ce8307fb 0 t/t5100/info0002 +100644 bd0d1221aa34973159390642efc67773a6bfef17 0 t/t5100/info0003 +100644 616c3092a28223d9aa1b3c68aa0bd49fc394dd85 0 t/t5100/info0004 +100644 46a46fc77283fe31bd583444f76ae2cb7085a35a 0 t/t5100/info0005 +100644 8c052777e0d216e84bb8464b1ceaff1bc7721154 0 t/t5100/info0006 +100644 49bb0fec85323c8c19182bc2a50ceb64ce8307fb 0 t/t5100/info0007 +100644 e8a295138317864b814b24af63ad3cf9c64cf79a 0 t/t5100/info0008 +100644 2a66321c8032cc0ee0cba25486e57f790d1f6ce8 0 t/t5100/info0009 +100644 1791241e46dc44994c58b365e339c093704a6006 0 t/t5100/info0010 +100644 b275a9a9b21c762814ed09c0d3a6eb103aa59370 0 t/t5100/msg0001 +100644 e2546ec7332b6b0483f4951906a40436da6383e0 0 t/t5100/msg0002 +100644 1ac68101b135f4aaf6dacd4bcc0b7586a29d6584 0 t/t5100/msg0003 +100644 6f8ba3b8e0e504d0aa343e45fffc044a211cc7ca 0 t/t5100/msg0004 +100644 dd94cd7b9f52247fd33a891e4fdd9b715c7f59b7 0 t/t5100/msg0005 +100644 b275a9a9b21c762814ed09c0d3a6eb103aa59370 0 t/t5100/msg0006 +100644 71b23c0236bddbedabfbdb982e4e73c5e266da28 0 t/t5100/msg0007 +100644 a80ecb97ef9dabb6d25d24d42212dabd5eab6249 0 t/t5100/msg0008 +100644 9ffe1314891f0998bf90f6c6b1e02c56c405e2f2 0 t/t5100/msg0009 +100644 a96c230092340037ef1c99ae91f24096eceacf44 0 t/t5100/msg0010 +100644 d7d680f631b14ea75adf34ba5052043be311e72f 0 t/t5100/nul-b64.expect +100644 16540d961f8e6eb791300e085d2d6e82e01bd2c4 0 t/t5100/nul-b64.in +100644 3d40691787b855cc0133514a19052492eb853d21 0 t/t5100/nul-plain +100644 8ce155167d51de1868fdd2395c312c46b9cc2c63 0 t/t5100/patch0001 +100644 8ce155167d51de1868fdd2395c312c46b9cc2c63 0 t/t5100/patch0002 +100644 8ce155167d51de1868fdd2395c312c46b9cc2c63 0 t/t5100/patch0003 +100644 196458e44e1974188cc1d1523a92c2bbef1ed506 0 t/t5100/patch0004 +100644 7d24b24af83c861a1dc615c19cbaa392fdd69dec 0 t/t5100/patch0005 +100644 8ce155167d51de1868fdd2395c312c46b9cc2c63 0 t/t5100/patch0006 +100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 t/t5100/patch0007 +100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 t/t5100/patch0008 +100644 65615c34af6ea70a53102e6bbef4bd60344cbf1c 0 t/t5100/patch0009 +100644 f055481d567212b7b43e7ca8251351b3f83b143b 0 t/t5100/patch0010 +100644 aba57f922b33b6ab708afbbf82c7e56f6e37bb8d 0 t/t5100/sample.mbox +100755 645583f9d729cb04ea7bd9638b0c49c48128822e 0 t/t5300-pack-object.sh +100755 073ac0c6f9dd3d06474b1b81c8c7b622dcfee663 0 t/t5301-sliding-window.sh +100755 0639772ac4e1e2c6563e793b16c2c10faf06758a 0 t/t5302-pack-index.sh +100755 31b20b21d23c2066fd124bf6a33dbdc689930170 0 t/t5303-pack-corruption-resilience.sh +100755 9fd9d0700033027921c90290cedc0a31d71c7733 0 t/t5304-prune.sh +100755 fb471a08c698b431cd2440e9d4f0e77e2fef6b08 0 t/t5305-include-tag.sh +100755 68c2ae688c2b7ff96ec927622f92fd512e7beefe 0 t/t5400-send-pack.sh +100755 ee769d6695ee91120671c485924d804f14c80424 0 t/t5401-update-hooks.sh +100755 1394047a8dc3e87476e223db42936d59845f803b 0 t/t5402-post-merge-hook.sh +100755 823239a251f9ba9607649382d595db1b6cc6dcb2 0 t/t5403-post-checkout-hook.sh +100755 c24003565d635722f07333bb662c8e102d577c9e 0 t/t5404-tracking-branches.sh +100755 86abc6227105e27fdb9ad99e34193efb90a6242f 0 t/t5405-send-pack-rewind.sh +100755 59e80a5ea253607bf83ac4eed670744df950eb81 0 t/t5406-remote-rejects.sh +100755 362cf7e928090fb3752936317f78a4d128810127 0 t/t5500-fetch-pack.sh +100755 16eadd6b68664884836976aafb6dcbb582603c09 0 t/t5502-quickfetch.sh +100755 4074e23ffa2c7a93fdbd4a367e273118224b9038 0 t/t5503-tagfollow.sh +100755 be9ee9326fc4590dcc875e31b6cf64b800451bc5 0 t/t5505-remote.sh +100755 13d1d826c20293c26c739c70c0a36ed48bbb41d1 0 t/t5510-fetch.sh +100755 22ba380034775e7584a33ca606294af34f568443 0 t/t5511-refspec.sh +100755 1dd8eed5bb3cb0f320a8f0780452e52fa7d8da16 0 t/t5512-ls-remote.sh +100755 9e7486274b3f0079cb993acbd03e90afc5638e38 0 t/t5513-fetch-track.sh +100755 8becbc3f38fde02371ebbcd9a39a320a1c00c290 0 t/t5515-fetch-merge-logic.sh +100644 2e0414f6c3d006ac6f06b884a13397c67f6b7df1 0 t/t5515/fetch.br-branches-default +100644 ca2cc1d1b44e3edc8cd42e2e77d0f85658a52195 0 t/t5515/fetch.br-branches-default-merge +100644 7d947cd80f9cf656024206f1ea31da0d9f10f493 0 t/t5515/fetch.br-branches-default-merge_branches-default +100644 ec39c54b7e242ddbeec76f55b98f555d562aa271 0 t/t5515/fetch.br-branches-default-octopus +100644 6bf42e24b67b526bac49e3cdb287e32513f4a6c4 0 t/t5515/fetch.br-branches-default-octopus_branches-default +100644 4a2bf3c95ca474417d1dd54d1ac0bcc02bb5f402 0 t/t5515/fetch.br-branches-default_branches-default +100644 12ac8d20fba1c8a9402b92aa71e2e6797101a042 0 t/t5515/fetch.br-branches-one +100644 b4b3b35ce0e2f46a16b015a74b771eb90ed3ebad 0 t/t5515/fetch.br-branches-one-merge +100644 2ecef384eb7d823104581bfe2b4bd240b449e5df 0 t/t5515/fetch.br-branches-one-merge_branches-one +100644 96e3029416b46ab4192d3e4aaa285a02489e4054 0 t/t5515/fetch.br-branches-one-octopus +100644 55e0bad621cde0c93e6a6fb92dc259c61986aba5 0 t/t5515/fetch.br-branches-one-octopus_branches-one +100644 281fa09d481a2d04788d5c1b0a67b8f569203ebc 0 t/t5515/fetch.br-branches-one_branches-one +100644 e2fa9c8654647e46baa89d24501b050209aefdc1 0 t/t5515/fetch.br-config-explicit +100644 ec1a7231aa7875df2cebd32411bad4861c233dcd 0 t/t5515/fetch.br-config-explicit-merge +100644 54f689151ff1006561309f6f7ca5e3523f8626c4 0 t/t5515/fetch.br-config-explicit-merge_config-explicit +100644 7011dfc18140aade896592c853bec85567d4ccc8 0 t/t5515/fetch.br-config-explicit-octopus +100644 bdad51f87163b51c378aaa1ca9bed8fe4a461af1 0 t/t5515/fetch.br-config-explicit-octopus_config-explicit +100644 1b237dde6e03b9bb1f4b9ad0b5ab75ae31127b57 0 t/t5515/fetch.br-config-explicit_config-explicit +100644 e75ec2f72b4420cf27bcb959ab83518d93e115b1 0 t/t5515/fetch.br-config-glob +100644 ce8f739a0d5f53d24d1408e0e39719c0411e3c0d 0 t/t5515/fetch.br-config-glob-merge +100644 5817bed8f88fcc11b9d5e67ce0863249339eeb2c 0 t/t5515/fetch.br-config-glob-merge_config-glob +100644 938e532db25e684599b39d1c862680a1caf8ea23 0 t/t5515/fetch.br-config-glob-octopus +100644 c9225bf6ff060118ae85b5c666085b3a558db16e 0 t/t5515/fetch.br-config-glob-octopus_config-glob +100644 a6c20f92ce6e708f915b285310a94b7080eab1e3 0 t/t5515/fetch.br-config-glob_config-glob +100644 83534d2ec85e30dee5c32b8d385dd717cf4930a2 0 t/t5515/fetch.br-remote-explicit +100644 a9064dd65a0e569cc3e7ce5ae2368313f6bfa7ec 0 t/t5515/fetch.br-remote-explicit-merge +100644 732a37e4d358f0239687770b28c109051c438070 0 t/t5515/fetch.br-remote-explicit-merge_remote-explicit +100644 ecf020d9298e7a031833e9986f2deb787712cb89 0 t/t5515/fetch.br-remote-explicit-octopus +100644 af7753101140d43e2a592ec37b88d57d53d45bde 0 t/t5515/fetch.br-remote-explicit-octopus_remote-explicit +100644 51fae567c8765fc6f447d7ee93a9796e022663a5 0 t/t5515/fetch.br-remote-explicit_remote-explicit +100644 94e6ad31e301a87b52160f2287b54051e27b4e18 0 t/t5515/fetch.br-remote-glob +100644 09362e25af564342ccb5ed9507caa103676aedf6 0 t/t5515/fetch.br-remote-glob-merge +100644 e2eabec62e12b29be2b8cd0c8e6d0cc9ad042f4d 0 t/t5515/fetch.br-remote-glob-merge_remote-glob +100644 b08e0461954dcedc90df43c03302e3d4257c6f4b 0 t/t5515/fetch.br-remote-glob-octopus +100644 d4d547c84733f0faacc85c88c7b7fa138933e4a6 0 t/t5515/fetch.br-remote-glob-octopus_remote-glob +100644 646dbc877008edae3644ceef92fd5bed990eb784 0 t/t5515/fetch.br-remote-glob_remote-glob +100644 65ce6d99e2631fd777de10e11b3a58db5859c29a 0 t/t5515/fetch.br-unconfig +100644 8258c808689d883b97867b3d07ca89e08146de12 0 t/t5515/fetch.br-unconfig_--tags_.._.git +100644 284bb1fb613cafff208741069aea2d4041ec07cd 0 t/t5515/fetch.br-unconfig_.._.git +100644 11eb5a6ef22d52b794dd7a0a3e29d2ff515791c2 0 t/t5515/fetch.br-unconfig_.._.git_one +100644 f02bab2fb4a1e07c6128fb899bf42183d1f0965e 0 t/t5515/fetch.br-unconfig_.._.git_one_tag_tag-one_tag_tag-three-file +100644 3f1be224b8b46ba18bb45ffdbc155d9bd312376c 0 t/t5515/fetch.br-unconfig_.._.git_one_two +100644 85de41109ecfb6fd4ac3a1b3b15489b83506a63c 0 t/t5515/fetch.br-unconfig_.._.git_tag_tag-one-tree_tag_tag-three-file +100644 0da2337f1b95b2011524b3d047a291747bd80305 0 t/t5515/fetch.br-unconfig_.._.git_tag_tag-one_tag_tag-three +100644 fc7041eefc2150417abc504e25f9d82049b3102e 0 t/t5515/fetch.br-unconfig_branches-default +100644 e94cde745b58aeb360e2db064466260f2362cf87 0 t/t5515/fetch.br-unconfig_branches-one +100644 01a283e70d9d10373c363d1fe15b39949bba8d89 0 t/t5515/fetch.br-unconfig_config-explicit +100644 3a556c5e964785046cc43e9c13351818f436b857 0 t/t5515/fetch.br-unconfig_config-glob +100644 db216dfa562e894999c565d44d43abb6c6dd562e 0 t/t5515/fetch.br-unconfig_remote-explicit +100644 aee65c204d1b1468adaee9d41dc5c0a47a3ae999 0 t/t5515/fetch.br-unconfig_remote-glob +100644 950fd078db7ccbe27825183cb0236a0dcc45139a 0 t/t5515/fetch.master +100644 0e59950c7b5e18521b59a17d3aaddc3a314a6f9c 0 t/t5515/fetch.master_--tags_.._.git +100644 66d1aaddae6cb34d83bbd0e728fbe89fd26227b1 0 t/t5515/fetch.master_.._.git +100644 35deddbd2ca6c28c7a50dfc28c3da09b79b7b6a6 0 t/t5515/fetch.master_.._.git_one +100644 82868524ca40041b243e146064f4451d6899f3c4 0 t/t5515/fetch.master_.._.git_one_tag_tag-one_tag_tag-three-file +100644 35ec5782c8a6076a76cf314fb5021da8a641e782 0 t/t5515/fetch.master_.._.git_one_two +100644 2e133eff296d291045e86fe6a8cba958106b9aae 0 t/t5515/fetch.master_.._.git_tag_tag-one-tree_tag_tag-three-file +100644 92b18b40ccc883bede4d2067aa4bf7a529d2f059 0 t/t5515/fetch.master_.._.git_tag_tag-one_tag_tag-three +100644 603d6d23312b6a58d95a685803d8b19f0e0ac4e2 0 t/t5515/fetch.master_branches-default +100644 fe9bb0b7982951ccf85934e42ff7c9620e2312b4 0 t/t5515/fetch.master_branches-one +100644 4be97c75752571d46aef0283f164abea025a1be8 0 t/t5515/fetch.master_config-explicit +100644 cb0726ff8d7ea9e801497b564b680710c2a273af 0 t/t5515/fetch.master_config-glob +100644 44a1ca84296079356eabc3fd37d04529a3492409 0 t/t5515/fetch.master_remote-explicit +100644 724e8db0a533248cab6784696c90db02ca1435f3 0 t/t5515/fetch.master_remote-glob +100644 21917c1e5dba7133d451403984f754f445657908 0 t/t5515/refs.br-branches-default +100644 21917c1e5dba7133d451403984f754f445657908 0 t/t5515/refs.br-branches-default-merge +100644 21917c1e5dba7133d451403984f754f445657908 0 t/t5515/refs.br-branches-default-merge_branches-default +100644 21917c1e5dba7133d451403984f754f445657908 0 t/t5515/refs.br-branches-default-octopus +100644 21917c1e5dba7133d451403984f754f445657908 0 t/t5515/refs.br-branches-default-octopus_branches-default +100644 21917c1e5dba7133d451403984f754f445657908 0 t/t5515/refs.br-branches-default_branches-default +100644 8a705a5df252f6f9d4cd32b1557a221ca4f7dc6f 0 t/t5515/refs.br-branches-one +100644 8a705a5df252f6f9d4cd32b1557a221ca4f7dc6f 0 t/t5515/refs.br-branches-one-merge +100644 8a705a5df252f6f9d4cd32b1557a221ca4f7dc6f 0 t/t5515/refs.br-branches-one-merge_branches-one +100644 8a705a5df252f6f9d4cd32b1557a221ca4f7dc6f 0 t/t5515/refs.br-branches-one-octopus +100644 8a705a5df252f6f9d4cd32b1557a221ca4f7dc6f 0 t/t5515/refs.br-branches-one-octopus_branches-one +100644 8a705a5df252f6f9d4cd32b1557a221ca4f7dc6f 0 t/t5515/refs.br-branches-one_branches-one +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-config-explicit +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-config-explicit-merge +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-config-explicit-merge_config-explicit +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-config-explicit-octopus +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-config-explicit-octopus_config-explicit +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-config-explicit_config-explicit +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-config-glob +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-config-glob-merge +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-config-glob-merge_config-glob +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-config-glob-octopus +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-config-glob-octopus_config-glob +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-config-glob_config-glob +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-remote-explicit +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-remote-explicit-merge +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-remote-explicit-merge_remote-explicit +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-remote-explicit-octopus +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-remote-explicit-octopus_remote-explicit +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-remote-explicit_remote-explicit +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-remote-glob +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-remote-glob-merge +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-remote-glob-merge_remote-glob +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-remote-glob-octopus +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-remote-glob-octopus_remote-glob +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-remote-glob_remote-glob +100644 13e4ad2e4602154808a384fe298497b220ac9d6a 0 t/t5515/refs.br-unconfig +100644 13e4ad2e4602154808a384fe298497b220ac9d6a 0 t/t5515/refs.br-unconfig_--tags_.._.git +100644 70962eaac152f7063e965da7c6a61abee410e0bc 0 t/t5515/refs.br-unconfig_.._.git +100644 70962eaac152f7063e965da7c6a61abee410e0bc 0 t/t5515/refs.br-unconfig_.._.git_one +100644 13e4ad2e4602154808a384fe298497b220ac9d6a 0 t/t5515/refs.br-unconfig_.._.git_one_tag_tag-one_tag_tag-three-file +100644 70962eaac152f7063e965da7c6a61abee410e0bc 0 t/t5515/refs.br-unconfig_.._.git_one_two +100644 13e4ad2e4602154808a384fe298497b220ac9d6a 0 t/t5515/refs.br-unconfig_.._.git_tag_tag-one-tree_tag_tag-three-file +100644 13e4ad2e4602154808a384fe298497b220ac9d6a 0 t/t5515/refs.br-unconfig_.._.git_tag_tag-one_tag_tag-three +100644 21917c1e5dba7133d451403984f754f445657908 0 t/t5515/refs.br-unconfig_branches-default +100644 8a705a5df252f6f9d4cd32b1557a221ca4f7dc6f 0 t/t5515/refs.br-unconfig_branches-one +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-unconfig_config-explicit +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-unconfig_config-glob +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-unconfig_remote-explicit +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.br-unconfig_remote-glob +100644 13e4ad2e4602154808a384fe298497b220ac9d6a 0 t/t5515/refs.master +100644 13e4ad2e4602154808a384fe298497b220ac9d6a 0 t/t5515/refs.master_--tags_.._.git +100644 70962eaac152f7063e965da7c6a61abee410e0bc 0 t/t5515/refs.master_.._.git +100644 70962eaac152f7063e965da7c6a61abee410e0bc 0 t/t5515/refs.master_.._.git_one +100644 13e4ad2e4602154808a384fe298497b220ac9d6a 0 t/t5515/refs.master_.._.git_one_tag_tag-one_tag_tag-three-file +100644 70962eaac152f7063e965da7c6a61abee410e0bc 0 t/t5515/refs.master_.._.git_one_two +100644 13e4ad2e4602154808a384fe298497b220ac9d6a 0 t/t5515/refs.master_.._.git_tag_tag-one-tree_tag_tag-three-file +100644 13e4ad2e4602154808a384fe298497b220ac9d6a 0 t/t5515/refs.master_.._.git_tag_tag-one_tag_tag-three +100644 21917c1e5dba7133d451403984f754f445657908 0 t/t5515/refs.master_branches-default +100644 8a705a5df252f6f9d4cd32b1557a221ca4f7dc6f 0 t/t5515/refs.master_branches-one +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.master_config-explicit +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.master_config-glob +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.master_remote-explicit +100644 9bbbfd9fc5332911f35826763b821d2eeb758d6f 0 t/t5515/refs.master_remote-glob +100755 f0030ad00e4a6478fcb3ccfc503e576bd58003bd 0 t/t5516-fetch-push.sh +100755 ea49dedbf8867694d83cd550c8212ff107361920 0 t/t5517-push-mirror.sh +100755 c6bc65faa06adeaced0733064fb09eb82add585e 0 t/t5518-fetch-exit-status.sh +100755 997b2db827c4f37512c6b5d2f861e12105e2a32d 0 t/t5520-pull.sh +100755 1a15817cd5f8e838812723ad14dbec59a108680c 0 t/t5530-upload-pack-error.sh +100755 f8c17cd96cc86ca8f2db2ff51467f712d65f8956 0 t/t5540-http-push.sh +100755 3c013e2b6aa5c659c80134baf43c99e0d89e2e38 0 t/t5600-clone-fail-cleanup.sh +100755 d785b3df78e8507d81ffa03ff694846791edfc87 0 t/t5601-clone.sh +100755 8367a6845f6ea3cdbc2f4f0e096144975fa3aef2 0 t/t5602-clone-remote-exec.sh +100755 1c109160690d273451f7a089be42e45f36a3b5bb 0 t/t5700-clone-reference.sh +100755 8dfaaa456e115e85e36c438bb998d8053534104e 0 t/t5701-clone-local.sh +100755 328e4d9a339101d04bb22e441a8955f6c2e1f260 0 t/t5702-clone-options.sh +100755 ef7127c1b3943a494692ac8027ec321608a31b9c 0 t/t5710-info-alternate.sh +100755 f55627b641682e72d58a2282639ca589b38fa744 0 t/t6000lib.sh +100755 b2131cdacd93e0b62f4ef8fdc62b6a81c6aef6fc 0 t/t6001-rev-list-graft.sh +100755 8f5de097ecd703ae5f6f889ecb735f7277f361be 0 t/t6002-rev-list-bisect.sh +100755 5daa0be8cc856bff513905bc6583854e0b5ae53a 0 t/t6003-rev-list-topo-order.sh +100755 5dabf1c5e354c28cc593bd0ea8e4b0d5f0d56d67 0 t/t6004-rev-list-path-optim.sh +100755 0b64822bf621dee5c9544f76013c0342412eaee6 0 t/t6005-rev-list-count.sh +100755 9176484db2f78122f71c0f11889e01382effcfb9 0 t/t6006-rev-list-format.sh +100755 4b8611ce2092f9062aeaeb62ce19a9121d5be537 0 t/t6007-rev-list-cherry-pick-file.sh +100755 c4af9ca0a7edf6230dc6ca8ec10848545971fce7 0 t/t6008-rev-list-submodule.sh +100755 c8a96a9a994badde602c8bf7a7decda048a00525 0 t/t6009-rev-list-parent.sh +100755 b6e57b2426728cce308a57315247cd2a66cabf4a 0 t/t6010-merge-base.sh +100755 e51eb41f4b9575d2b51d8d4d255ff5ab7a0889ad 0 t/t6011-rev-list-with-bad-commit.sh +100755 a19d49de28c457d1a5726400d25c1f731506a05f 0 t/t6020-merge-df.sh +100755 331b9b07d4eedb07377de605ebb87691427b7bb4 0 t/t6021-merge-criss-cross.sh +100755 e3f7ae8120aa2a46b25dd3830597cb863a5f5e20 0 t/t6022-merge-rename.sh +100755 f674c48cab3e80d63b5a5831c667b8e08b542905 0 t/t6023-merge-file.sh +100755 65be95fbaaef4861189a7fc6b3a25bb6d7feb0a7 0 t/t6023-merge-rename-nocruft.sh +100755 802d0d06ebddec9db6e3a109e689b3974f1e0ff1 0 t/t6024-recursive-merge.sh +100755 fc58456a11eef7ecb4cf60d37a9e9d5cbe13f970 0 t/t6025-merge-symlinks.sh +100755 56fc34176859b81137b4d88af90398b9a74a18f7 0 t/t6026-merge-attr.sh +100755 92ca1f0f8ccabe6f01159ea3e4a73683387ec4a3 0 t/t6027-merge-binary.sh +100755 f8f3e3ff2c00df468f5703a4e0ac31f52e42e06d 0 t/t6028-merge-up-to-date.sh +100755 5bbfa44e8d9ee3eebb18eb07e93380c802741a18 0 t/t6029-merge-subtree.sh +100755 244fda62a5cd34d778cf0789961654eaa37fe589 0 t/t6030-bisect-porcelain.sh +100755 8073e0c3efb2ac01e4a81e722fc357bcab13f5d4 0 t/t6031-merge-recursive.sh +100755 eac5ebac24a0174fa20625a19835861573147a26 0 t/t6032-merge-large-rename.sh +100755 75d9602de4d4238b4182956127540525f711d33f 0 t/t6033-merge-crlf.sh +100755 aac212e936331db9a596fda0c4a9c0382e123797 0 t/t6040-tracking-info.sh +100755 919552a2fc5544c130268befca322a6e6a8081c3 0 t/t6101-rev-parse-parents.sh +100755 2fb672c3b43a9efe4cb9c85465f6b33f23724e48 0 t/t6120-describe.sh +100755 bc74349416d858834c43f6c648daa95c8b9f3a7a 0 t/t6200-fmt-merge-msg.sh +100755 a3c8941c726d77fd993a3cfcd7fde4e9aa43da74 0 t/t6300-for-each-ref.sh +100755 910a28c7e29b6dd8bd30d1ccb156681b44e51bca 0 t/t7001-mv.sh +100755 c8b4f65f380f3941c75bd6ed52975777d2b28d67 0 t/t7002-grep.sh +100755 a0ab096c8fdee153a89a1428f85c9bf107badada 0 t/t7003-filter-branch.sh +100755 bc7ce2cbbb712f890245688da03be96146a1d9ed 0 t/t7004-tag.sh +100644 83855fa4e1c6c37afe550c17afa1e7971042ded5 0 t/t7004/pubring.gpg +100644 8fed1339ed0a744e5663f4a5e6b6ac9bae3d8524 0 t/t7004/random_seed +100644 d831cd9eb3eee613d3c0e1a71093ae01ea7347e3 0 t/t7004/secring.gpg +100644 abace962b8bf84be688a6f27e4ebd0ee7052f210 0 t/t7004/trustdb.gpg +100755 2d919d69ef110408b820c76185d6b8da63ea183e 0 t/t7005-editor.sh +100755 d8a7c798525728ddc8fc5fa9bd8335d8d1f0a710 0 t/t7010-setup.sh +100755 0d9874bfd7082f9ef16c1f6b3ff8a848a19d8937 0 t/t7101-reset.sh +100755 29f5678b4c93485ad492fa865a5da58a3cc05b7c 0 t/t7102-reset.sh +100755 cdecebe456c7a9cf30465b112a24ce7bcf76f344 0 t/t7103-reset-bare.sh +100755 f136ee7bb5300966c0c3c9d2250dc81763db9feb 0 t/t7104-reset.sh +100755 9ad5d635a2881c920fff8e524aea0ed931f68e6c 0 t/t7201-co.sh +100755 2b51c0d7d8ab727a5fb0be8338938f1d3b2eb6a3 0 t/t7300-clean.sh +100755 cbc0c34ce2487959ef0e8f89f7c2a5d4a68be826 0 t/t7400-submodule-basic.sh +100755 bf12dbdeef6e307850a91eb6be5ebe537b2de0c8 0 t/t7401-submodule-summary.sh +100755 f919c8d34de41b2ec3fe08c217dd2c6276cf8472 0 t/t7402-submodule-rebase.sh +100755 d89f91a6fb7fa12d41cc4a346829bff7cbd3e76b 0 t/t7500-commit.sh +100755 a72e65c8910f71b8ea562ef5ed641c54fbd89a8e 0 t/t7500/add-comments +100755 2fa3d86a108d1fc410d82173809066db0b32b062 0 t/t7500/add-content +100755 e1d856af6d8b59fed20e3beb272006612ba4e8a5 0 t/t7500/add-signed-off +100755 0edd9ddf73b7053c21595ce1ac1dd157c77d1bca 0 t/t7501-commit.sh +100755 f1112639b698faca38b2c8fc743e422d14dc15f6 0 t/t7502-commit.sh +100755 38a48b57c70a888838cfa114be843e1d4aea00d8 0 t/t7502-status.sh +100755 b06909599564a1c8afa027b0f9c71ef6bb61d6e4 0 t/t7503-pre-commit-hook.sh +100755 47680e6df41c2bc14f23514b010a8aefb3fedcd7 0 t/t7504-commit-msg-hook.sh +100755 cd6c7c834218fd4c46c49396b79da1ddeef42772 0 t/t7505-prepare-commit-msg-hook.sh +100755 a75130cdbb55be157c915f4fc1397227a78441ec 0 t/t7506-status-submodule.sh +100755 5eeb6c2b2708d582a6e86cd2e06e2b00b7b7b391 0 t/t7600-merge.sh +100755 55aa6b5f2716255b2b5aa74f1cac9d285de6d6a8 0 t/t7601-merge-pull-config.sh +100755 fcb8285746420ed721713d9c8e527d23cafb05cf 0 t/t7602-merge-octopus-many.sh +100755 17b19dc11f2b1a5d26a16f447733880f3cf30d26 0 t/t7603-merge-reduce-heads.sh +100755 6081677d234f1fcb88b6b9160f707ebf0274f38a 0 t/t7604-merge-custom-message.sh +100755 ee21a107fd99e35b9d6325a040b7549b7a6c2502 0 t/t7605-merge-resolve.sh +100755 9285071c473dcfe7d37845d01ba20226b5ab585d 0 t/t7610-mergetool.sh +100755 31c340fd389ed2688bb94a29acbf892be6f0c564 0 t/t7701-repack-unpack-unreachable.sh +100755 eabec2e06e2f97fc1790cd4ce30a80e402d4a205 0 t/t8001-annotate.sh +100755 92ece30fa94784bdad8ae50fc370487e60bbcb5c 0 t/t8002-blame.sh +100755 966bb0a61a89ed63dec085338d3c45f766a7f777 0 t/t8003-blame.sh +100755 ba19ac127e630c01e009fa6eda1fac0086d7184d 0 t/t8004-blame.sh +100755 1c857cf4ab6e359d7009d2c6b5018bb61c916e93 0 t/t9001-send-email.sh +100755 843a5013b96c675a629bd7f738eca220861e6ff8 0 t/t9100-git-svn-basic.sh +100755 f420796c31db2746b71ba9d7090f37363eba214a 0 t/t9101-git-svn-props.sh +100755 0e7ce34b9b1e254873a2700cf58095318b49b15c 0 t/t9102-git-svn-deep-rmdir.sh +100755 9ffd8458ef9d58fa5d3c42fd61f4629219b4d80a 0 t/t9103-git-svn-tracked-directory-removed.sh +100755 4d964e2db7cc3c96fc64911bd58c4f2f9679a6cd 0 t/t9104-git-svn-follow-parent.sh +100755 63230367bb1566384e66e1b5ddd6a68e1ae98c8f 0 t/t9105-git-svn-commit-diff.sh +100755 83896e96876d8cca24496c7cb278732a308e3d92 0 t/t9106-git-svn-commit-diff-clobber.sh +100755 bc37db9d62071ba92463276524675964c3e91593 0 t/t9106-git-svn-dcommit-clobber-series.sh +100755 d9b553ad55b1f7024af0689a450a9c6c65dcb034 0 t/t9107-git-svn-migrate.sh +100755 f6f71d0545c869a7216eb0e81f260085f6ffdec1 0 t/t9108-git-svn-glob.sh +100755 04d2a65c087de78fa8126b68774673532497276e 0 t/t9110-git-svn-use-svm-props.sh +100644 cc799c238de91a2b466735d678a0bc7415ebefc2 0 t/t9110/svm.dump +100755 a8d74dcd3aba7c462d46ea33c722d4307d24bded 0 t/t9111-git-svn-use-svnsync-props.sh +100644 499fa9594fafe5d45a32005c189634b6a3048777 0 t/t9111/svnsync.dump +100755 d470a920e4864ab0c494da1261fe835ff80474eb 0 t/t9112-git-svn-md5less-file.sh +100755 8da8ce58eb1b29210a6ac95fdd3a3fcb547ca36f 0 t/t9113-git-svn-dcommit-new-file.sh +100755 61d7781616eed4374c014cabd75a297c2baa348d 0 t/t9114-git-svn-dcommit-merge.sh +100755 f0fbd3aff7e63f64f8ba388db805013c43b4b22c 0 t/t9115-git-svn-dcommit-funky-renames.sh +100644 42422f791ea4240495e0b9cb5173bb7a27989958 0 t/t9115/funky-names.dump +100755 4b2cc878f685e65b2ccd5d8153efb533320d6ee9 0 t/t9116-git-svn-log.sh +100755 7a689bb1cd1d9daa1f17c0dcaaafa4d68ebd78fa 0 t/t9117-git-svn-init-clone.sh +100755 3281cbd3472a8da58c4f6f0f3965b5810705b0e9 0 t/t9118-git-svn-funky-branch-names.sh +100755 cc619115931cb74a85a171ade915ca2c47639c9b 0 t/t9119-git-svn-info.sh +100755 5979e133b9d5b9d85ddca31a40763ed4fb6feba3 0 t/t9120-git-svn-clone-with-percent-escapes.sh +100755 99230b08107102836f752c14e1b0a67804b35ea3 0 t/t9121-git-svn-fetch-renamed-dir.sh +100644 5f9127be92616ea8fb8ace1cff80a37037cb15ec 0 t/t9121/renamed-dir.dump +100755 1190576a658d08a680e177b748cfc5e69caa3ddb 0 t/t9122-git-svn-author.sh +100755 c18878fad16a6565fe846cc958417fea73289dce 0 t/t9123-git-svn-rebuild-with-rewriteroot.sh +100755 8223c5909e6ff6936cb0ccf4d0badfe43491af46 0 t/t9124-git-svn-dcommit-auto-props.sh +100755 3e32e84e6cd32413f98b5189f869bfb8f0a7f354 0 t/t9200-git-cvsexportcommit.sh +100755 1fc06c5a23b50d54c33755a9fce4ddd9ed3b9c79 0 t/t9300-fast-import.sh +100755 c19b4a2bab586b21da43c7a838ba85626f913568 0 t/t9301-fast-export.sh +100755 4b91f8d4c45dad075d69028c9c70aa9cb1959e2b 0 t/t9400-git-cvsserver-server.sh +100755 e27a1c5f85bbecac652ce8d224f8fc5e99b02a4e 0 t/t9401-git-cvsserver-crlf.sh +100755 ae7082be1d903e1f4d5758610d5166152f2847cc 0 t/t9500-gitweb-standalone-no-errors.sh +100755 0d7786a8c730d17fa194346f1da2978d23256da9 0 t/t9600-cvsimport.sh +100755 9706ee5773692bd8fcfbc9015ef062947c0a2da5 0 t/t9700-perl-git.sh +100755 4d2312548a81762918ac05b9a0014195b08ea532 0 t/t9700/test.pl +100644 11c027571b44c429b4f6fdca88bff9c3360c7545 0 t/test-lib.sh +100644 7b181d15cebb4c86a6ad7fed3dbf30ce2223b4c5 0 t/test4012.png +100644 7b181d15cebb4c86a6ad7fed3dbf30ce2223b4c5 0 t/test9200a.png +100644 ac22ccbd3ee9f03a3b38249ac8efdbe96b5da2cd 0 t/test9200b.png +100644 4470d2bf78e1fbb00d00e487f41daa4373cf48e1 0 tag.c +100644 7a0cb0070d46ba8c49d71029dc0704188805ea62 0 tag.h +100644 3467705e9b0e14a0230473186079e83a582e4345 0 tar.h +100644 6759ecbf98f8a90d96b4918c130babdd87889f69 0 templates/.gitignore +100644 9f3f1fc352dea624bd36e55802de190ead0ad9dd 0 templates/Makefile +100644 fae88709a636f3a06cc813dd64b28bfee7fa2073 0 templates/branches-- +100755 8b2a2fe84feaeaba56953d6d4d0d649b3cf755eb 0 templates/hooks--applypatch-msg.sample +100755 6ef1d29d09a10a5b6c3cbec0ac481931cd0d85fc 0 templates/hooks--commit-msg.sample +100755 22668216a3cec5a00d804dc5e9a904a10fd0fd09 0 templates/hooks--post-commit.sample +100755 18d2e0f72768c103d593cc2cf6d2b7a4bc8a9a01 0 templates/hooks--post-receive.sample +100755 5323b56b81b9dd3d7f9fb86d8892241becbb5e7e 0 templates/hooks--post-update.sample +100755 b1f187c2e9acaba942639bca90a63c5b4f058967 0 templates/hooks--pre-applypatch.sample +100755 0e49279c7f7b805c78f1bc4760a0d1c70a84a0d9 0 templates/hooks--pre-commit.sample +100755 be1b06e25043146f22261b55548229e6ab524b7c 0 templates/hooks--pre-rebase.sample +100755 365242499dcf0ee35c26ccb2917724d6e559be69 0 templates/hooks--prepare-commit-msg.sample +100755 93c605594fc06683088b934273873165215ccbb5 0 templates/hooks--update.sample +100644 2c87b72dff61f8394b3f1f32e21c1d936314ec2e 0 templates/info--exclude +100644 c6f25e80b8bcf0a21db2bea368b9e444c19bc0bf 0 templates/this--description +100644 90da448ebec3e5375b7725ba7f297c1c74199b87 0 test-chmtime.c +100644 62e8f2387a1cab97ec1c71d1993d082274e17bf5 0 test-date.c +100644 3d885ff37ee7fc43dec05dd827679d68cee5516b 0 test-delta.c +100644 8cefe6cfed87c8fe0c11d1263dae01639d2bd0f0 0 test-genrandom.c +100644 a3c4688778d9db28c83c9149c9cff1609b69b93f 0 test-match-trees.c +100644 2a79e729a4018ddb2da9ff633f4bf3b102fa8f88 0 test-parse-options.c +100644 a0bcb0e210523124fa977c8bf46667cf25d0335f 0 test-path-utils.c +100644 78d7e983a7a05ba0652132425a66477ef5773304 0 test-sha1.c +100755 0f0bc5d02f4dcbd67c6d405350e5aaeb39f44bfb 0 test-sha1.sh +100644 55e7e2904eb5f95cedaec2520ddd1d158ee93c7a 0 thread-utils.c +100644 cce4b77bd6452e2ec589d8c0dc0e8156352dd67b 0 thread-utils.h +100644 4713f9165c54405d51e81c3e90847120ee907e5d 0 trace.c +100644 6eb65b873afc9dfd457e974b63d88350bb8dc913 0 transport.c +100644 d0b52053fff9bc463438674232bffb6024f3b1fc 0 transport.h +100644 bbb126fc46cfb28a0bc92cc0842c0dc72017751d 0 tree-diff.c +100644 02e2aed7737207225f1b96eed774a1b75dd6d8d9 0 tree-walk.c +100644 42110a465f9a8c91d1bc643dfae7a9b9c32e3719 0 tree-walk.h +100644 03e782a9cabc0a12ed5baec0ef59c99f19dbc843 0 tree.c +100644 2ff01a4f839ecc2206fcc1c13fee9d5d202b1128 0 tree.h +100644 bcdc8bbb3b44a43aa43db6035a31478158e070af 0 unpack-file.c +100644 cba0aca062f201c5cd5f8799f2190d4a6f06e7c7 0 unpack-trees.c +100644 94e567265af9a69a30dd5c578439b6444e50004d 0 unpack-trees.h +100644 7e8209ea4b43995737b36bc58db47e7dd6eadb19 0 update-server-info.c +100644 c911e70c9aa47b70dac41b7f4de2f0b4b6c1f948 0 upload-pack.c +100644 a5fc4ec5fae66823266862fa0254474696c220e6 0 usage.c +100644 dc3735364f85273c2a119b994ddb405c09dc395c 0 utf8.c +100644 98cce1b038a908bec51ccd2f7e1c1f648cb429a1 0 utf8.h +100644 724ba87a7c9ccb16bc506fc3f25710a4b78e3006 0 var.c +100644 0e68ee6d2e2fb1b866ecec00c5f6446af366a62e 0 walker.c +100644 8a149e11084eeec4501b5b2c5d22e5266f4852e7 0 walker.h +100644 93562f03eef21b26945d2d9bbdc96818f4de6567 0 wrapper.c +100644 4c29255df1b637f93ab3d59e0dcab1fa3b40e10b 0 write_or_die.c +100644 7a7ff130a34942506e6068105ac5946c9404bf18 0 ws.c +100644 889e50f89fc24984f700d14f7033600fa9fdf642 0 wt-status.c +100644 78add09bd67c727babb61cd1eaa773bcd0c6e55e 0 wt-status.h +100644 61dc5c547019776b971dc89d009f628bbac134fd 0 xdiff-interface.c +100644 f7f791d96b9a34ef0f08db4b007c5309b9adc3d6 0 xdiff-interface.h +100644 413082e1fdf537d230a0f58940cee7466b965d0e 0 xdiff/xdiff.h +100644 1bad8462fb32cffdc9ff20a278d513e7a444b257 0 xdiff/xdiffi.c +100644 3e099dc445d6130f6a0ce2c6270a3b06d6ee119f 0 xdiff/xdiffi.h +100644 d3d9c845c6420e4881636d779c7029f900a0b067 0 xdiff/xemit.c +100644 440a7390fa4abb0411c336cfba616e3229484e86 0 xdiff/xemit.h +100644 526ccb344d231fb978f53b80deb17ec6c8fed368 0 xdiff/xinclude.h +100644 8ef232cfad12d706aaafe705bf16c546f3597182 0 xdiff/xmacros.h +100644 82b3573e7ada8c6df13ac24a78650b80af91ea73 0 xdiff/xmerge.c +100644 e87ab57c652b56b1a684e2a0a56885c1d1b27ef7 0 xdiff/xprepare.c +100644 8fb06a537451cbf3335ab4bdacb0f992e9744338 0 xdiff/xprepare.h +100644 2511aef8d89ab52be5ec6a5e46236b4b6bcd07ea 0 xdiff/xtypes.h +100644 d7974d1a3e612a235b0c8adfde08ba802e782b5a 0 xdiff/xutils.c +100644 d5de8292e05e7c36c4b68857c1cf9855e3d2f70a 0 xdiff/xutils.h diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.lstree b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.lstree new file mode 100644 index 000000000..2d1fad46f --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/gitgit.lstree @@ -0,0 +1,331 @@ +100644 blob 6b9c715d21d5486e59083fb6071566aa6ecd4d42 .gitattributes +100644 blob a213e8e25bb2442326e86cbfb9ef56319f482869 .gitignore +100644 blob 373476bdc03f718b4c01471dd9996ee4497f43a8 .mailmap +100644 blob 9651afc89d5e789abd9cedaa4f3b92dde7dd1412 .project +100644 blob 6ff87c4664981e4397625791c8ea3bbb5f2279a3 COPYING +040000 tree fe83e22f0c04b81c62a335ef09d59c23ff93d6ca Documentation +100755 blob cb7cd4b53827fa6820e84b1318572d0115b3b17f GIT-VERSION-GEN +100644 blob 7d0c2c2f865d6ed969038e7543dbeb8933723ec3 INSTALL +100644 blob 52c67c1a472455dcce5c19a21bbfd0520ff7dd26 Makefile +100644 blob 548142c327a6790ff8821d67c2ee1eff7a656b52 README +120000 blob b9a53c3416991b66e1d35c2bbf663b48340b0041 RelNotes +100644 blob 0d561246e0a958d9a7284409b1900a82876eebf3 abspath.c +100644 blob ccb1108c94436035d0da8b1d6f08f859b68294a3 alias.c +100644 blob 216c23a6f854c614d38c743cd7687a37f304161b alloc.c +100644 blob 13029619e5ec34bac4ba61a6fc08800ab36f4a1b archive-tar.c +100644 blob cf285044e3576d0127c3215cb1253443d67517dc archive-zip.c +100644 blob f834b5f51f4cf5d3b73d21dfd99198caef3b19f8 archive.c +100644 blob 0b15b35143fffcc13764e4e668ee452b191cc609 archive.h +040000 tree 36753f6455e2308e746447cde2c2113285bc9b02 arm +100644 blob 17f6a4dca521d9690377f2e93a0192d8a874d2ad attr.c +100644 blob f1c2038b0923d3130937eef965667204a8634e6d attr.h +100644 blob b88270f90844095b3d352cc4213cbebd95a7f420 base85.c +100644 blob bd7d078e1ae5fe4ce0a16fda62a2c1743237941b blob.c +100644 blob ea5d9e9f8b63be2c7048d19ee53feb06b0795c80 blob.h +100644 blob b1e59f2196b933ab7169a30efc5d1d340f8f9c5c branch.c +100644 blob 9f0c2a2c1fab9a312f436880956da0973c68ead8 branch.h +100644 blob fc3f96eaefff91e4e85adb92162716939f0ecd72 builtin-add.c +100644 blob fc43eed36b55e4966796490b8c0a02fae790229c builtin-annotate.c +100644 blob 2216a0bf7cd53adc31346f66a3b9786a1d688bad builtin-apply.c +100644 blob 22445acbfc5279f391ac6afa855b21064ec54535 builtin-archive.c +100644 blob 8b6b09b10b8f9dcda0b7224f31c860bb803945f0 builtin-blame.c +100644 blob b1a2ad7a6b3b150cda8d031a87352a4daedc40ea builtin-branch.c +100644 blob ac476e7a4b45fc55b6b6d1e4d02be0c35aba2c7b builtin-bundle.c +100644 blob 7441a56acdbefdd8044a406f4d756ce8a4f06644 builtin-cat-file.c +100644 blob cb783fc77e75515a02ed2268dfb37ee3bbd03749 builtin-check-attr.c +100644 blob fe04be77a9312c11fa054897c5982fa6c74b8e5e builtin-check-ref-format.c +100644 blob 71ebabf9903bd90b7da59c47f1c0819b5f25c538 builtin-checkout-index.c +100644 blob 411cc513c65ba854221ad52dd6aeaaac7d213c9d builtin-checkout.c +100644 blob 48bf29f40a5e06fd588b34c468535e04abcf206b builtin-clean.c +100644 blob e086a40b41810c30a4f5228daa4e38857dae84d5 builtin-clone.c +100644 blob 7a9a309be0543da7d27e7710ef82271f2582e0a9 builtin-commit-tree.c +100644 blob f7c053a0106c2e42833d0d7c7255b7b636d09a93 builtin-commit.c +100644 blob 91fdc4985d8e64fae12209174dd4aa2d887793e5 builtin-config.c +100644 blob 91b5487478998e39bb8ae4a5cb667360cff82c9a builtin-count-objects.c +100644 blob ec404c839b6542deb4e15ca342fd3c0afbbedd2e builtin-describe.c +100644 blob 9bf10bb37e2f56ec2a10239d7419db8fbb641745 builtin-diff-files.c +100644 blob 17d851b29ee5de33e01745eabcd5cd735c30b352 builtin-diff-index.c +100644 blob 415cb1612f5322da89850874ba81885e41808678 builtin-diff-tree.c +100644 blob 7ffea975059f9e13b07ca680e6707ffc14973f90 builtin-diff.c +100644 blob 070971616dbb12d005c5c9a1f82cc5b0c5391f62 builtin-fast-export.c +100644 blob 7460ab7fce2a4e6a7e014f448819140e2204ccb7 builtin-fetch--tool.c +100644 blob 273239af3be61736ee4ff484d628950c4de7311a builtin-fetch-pack.c +100644 blob 7eec4a0e43ad5760f1060a7d5bcf2a5083015130 builtin-fetch.c +100644 blob df02ba7afdd615492361a1897a9dedd6ab233c96 builtin-fmt-merge-msg.c +100644 blob 445039e19c75e4c9321f7ee64289ef8201a25c14 builtin-for-each-ref.c +100644 blob 6eb7da88d3e8591a8c544acc61a42e00babff120 builtin-fsck.c +100644 blob fac200e0b08360625afc81b02913128c9b87f486 builtin-gc.c +100644 blob 631129ddfd0ffe06f919882d22cfc494d9553f50 builtin-grep.c +100644 blob 3a062487a7eacd01ed824b46a9124dd343cd2e60 builtin-http-fetch.c +100644 blob baf0d09ac4ea372b4015d399560a133b401b55cc builtin-init-db.c +100644 blob f4975cf35f7f1555739f7657ee62ed983d18cb84 builtin-log.c +100644 blob e8d568eed7ab700bc338af8f589d2f61e81f323c builtin-ls-files.c +100644 blob c21b841e7c5e8d27a6e66e7f70786d77aa4653b5 builtin-ls-remote.c +100644 blob d25767a1f7eb0a8b45bc1eed6b9aa95de0847f18 builtin-ls-tree.c +100644 blob f974b9df968c74c5d62d58b2a09493e6abb4322e builtin-mailinfo.c +100644 blob 71f3b3b8741e505fc652e6c74c75972f19211f71 builtin-mailsplit.c +100644 blob 3382b1382a7dcbd525126a35209072da4b4d8041 builtin-merge-base.c +100644 blob 3605960c2d9692514a6df0f344f3c3269cf1de3c builtin-merge-file.c +100644 blob 8f5bbaf402e020e308e7af9cedb7df1b2ec5a2b7 builtin-merge-ours.c +100644 blob 43e55bf90154c51b94527b2ab7eb7c60fe36e9ec builtin-merge-recursive.c +100644 blob dde0c7ed33118ff8d0cc421e8a0366e342c6d011 builtin-merge.c +100644 blob 4f65b5ae9baf66953e79886fd93fe31786b24d36 builtin-mv.c +100644 blob 85612c4dcb719b460623204046e35486e9d9fe97 builtin-name-rev.c +100644 blob 2dadec1630c266bbaf42e84810f7059ed5c43b1e builtin-pack-objects.c +100644 blob 34246df4ec946273d9f42e6f0848b02d8510beea builtin-pack-refs.c +100644 blob 10cb8df8457fd5f2ba9be62ecd0f9384e21c3e63 builtin-prune-packed.c +100644 blob 947de8cf258c73d8a327ef3a1daed417ba533f1b builtin-prune.c +100644 blob c1ed68d938f67343c6938cfef54d5ff69a522a63 builtin-push.c +100644 blob 72a6de302f88728af17ce5c5c6983c5267afc6f6 builtin-read-tree.c +100644 blob 0c34e378199064e87aa09caf0fa0a2346333ec69 builtin-reflog.c +100644 blob 54d1c3e3d16b2cebcff0c6c57d98756e48472b67 builtin-remote.c +100644 blob dd4573fe8dcd9dc8edd5a7d41bc8daa83034ee7e builtin-rerere.c +100644 blob c24c21909194014b467c86fd3598796e7db576b3 builtin-reset.c +100644 blob 893762c80f4910fadf2d6df414bd835cccb7faaa builtin-rev-list.c +100644 blob 9aa049ec170b0125fddde29adda3c720c8a7b8ee builtin-rev-parse.c +100644 blob e9da870d22c14c32a0e0a6cb71b933c79a2d8b53 builtin-revert.c +100644 blob ee8247b08cd007f73d5dfffa560a9efe33d327b9 builtin-rm.c +100644 blob 7588d22885d0af24ae80f1d687ccd097fe365021 builtin-send-pack.c +100644 blob d03f14fdad3d17dde06734d78ddb4aade6ed4f2b builtin-shortlog.c +100644 blob 233eed499d0b8790781326ff0455bdc7f09fe4d4 builtin-show-branch.c +100644 blob add16004f11375b1ad2b97f9b1bf1ced5c437f81 builtin-show-ref.c +100644 blob c0b21301ba4c126a49ed38b6983756b99a25aae0 builtin-stripspace.c +100644 blob bfc78bb3f6eff2f8e39649b9649ae7263f143ad9 builtin-symbolic-ref.c +100644 blob 325b1b2632e44121c23bc6df556bf3aa4e32ba04 builtin-tag.c +100644 blob f4bea4a322c26a54734286073c5e67444555c2d9 builtin-tar-tree.c +100644 blob a8918666655bb91f952ccdac18715bd9ba4a09f2 builtin-unpack-objects.c +100644 blob 38eb53ccba2b97a0fccf50d6ba0b7424fe2d1bcb builtin-update-index.c +100644 blob 56a0b1b39cf4c4fc51dbbff256240655bc36a038 builtin-update-ref.c +100644 blob a9b02fa32f372a6810867c10560a20d58b5b2a91 builtin-upload-archive.c +100644 blob f4ac595695b1fff1317ff7d14ea9427780327aea builtin-verify-pack.c +100644 blob 729a1593e61d87ad4596f07e7faedac81de64e81 builtin-verify-tag.c +100644 blob 52a3c015ff8e4611522bd41078bdb2934d288d35 builtin-write-tree.c +100644 blob f3502d305e4f65e9707fe8b738f64be6e49f7f84 builtin.h +100644 blob 00b2aabefca49b634f49143523ee31556baa7777 bundle.c +100644 blob e2aedd60d6ad1482bb6da173c853e6ba4805c8d7 bundle.h +100644 blob 5f8ee87bb1c446341b640c2f978a658d6bfcfcd0 cache-tree.c +100644 blob cf8b790874c4a4f5890b360c237ccdd4a5a03de4 cache-tree.h +100644 blob 2475de9fa837596303284157e08b3080d64351ee cache.h +100755 blob d6fe6cf1749ebcd6189fa36cbb4e14a532d2d17b check-builtins.sh +100644 blob 00d92a16631a80ff8ec4e995dafcd3e55434fad5 check-racy.c +100755 blob a1c4c3e8d845e8e791d7df0c1387e1b2262b5ecf check_bindir +100644 blob fc0b72ad59b13e4bd86372e5e81b4f400c99d26e color.c +100644 blob 6cf5c88aaf8d0e38e2853e6fd212e3cdd6c180cb color.h +100644 blob 9f80a1c5e3a461afd11966625589684d61187911 combine-diff.c +100644 blob 3583a33ee90647d8e6ded02643eb75753760d94f command-list.txt +100644 blob dc0c5bfdab7296bf7febb6f1b1aad64550838c15 commit.c +100644 blob 77de9621d9c926c6fb8a2bf9ca81c6c376a2ad41 commit.h +040000 tree 3612b1a756de51eb5beb16e0c3b958e74f8bc53d compat +100644 blob 53f04a076a7275965090edd4ca2a34652c4f5679 config.c +100644 blob b776149531025c85f5665d971e6e072f0cc64893 config.mak.in +100644 blob 7c2856efc92ca55e3cf03fcf1c72ffb70318f7c3 configure.ac +100644 blob 574f42fa47ffa69328217eb25afee6f85db9595e connect.c +040000 tree ed65cee05eb473aca9cfae781f109d464dcbd711 contrib +100644 blob 78efed800d4d64898d438d9590b01be008cfcd36 convert.c +100644 blob e54d15aced7595ccb11423b0de121db9051ad1f3 copy.c +100644 blob ace64f165e4a01fb99892e9b89e7df791a7f4ca1 csum-file.c +100644 blob 72c9487f4fd9fcab5e02fc2dc6afd3cb7f9c036a csum-file.h +100644 blob ee06eb7f48f1d3e818b3037369b4e056fe7e5be7 ctype.c +100644 blob 4540e8df5ab8bc8ff66549144d7db2928e12199b daemon.c +100644 blob 35a52576c53e5e1406d40ed4402b8834a29b9f0e date.c +100644 blob d9668d2ef94c73e4a7a5602011ff13a9fd9d8c6a decorate.c +100644 blob 1fa4ad9beb08f23888814b99183487ab85378bfd decorate.h +100644 blob 40ccf5a1e95f62d840a006274f7024fa43208b1c delta.h +100644 blob a4e28df714b4834e5efe42fa3abb647711913d71 diff-delta.c +100644 blob e7eaff9a68ccbcc692522c9956f0dae9af45f3f1 diff-lib.c +100644 blob 7d68b7f1bef1039b4996e662fb17968c4e3e3e79 diff-no-index.c +100644 blob cbf25473c594abfd1fc13473108dc9c15e2f1d15 diff.c +100644 blob 50fb5ddb0bec02b0cd5498d6ecc37d44bf874476 diff.h +100644 blob 31cdcfe8bcdae7df65b0387071846299a14bb7be diffcore-break.c +100644 blob e670f8512558c38d9a9d6e754cfc609b042b1195 diffcore-delta.c +100644 blob 23e93852d8c701760d56e7e728dba7c08367fbe8 diffcore-order.c +100644 blob af9fffe6e8e145b066157da8791c749257e7c8e9 diffcore-pickaxe.c +100644 blob 1b2ebb40014d820fe4fb679509ab694d453be7b4 diffcore-rename.c +100644 blob cc96c20734bf4184970f5381416637cf6e45ea13 diffcore.h +100644 blob 29d1d5ba31def46ba8b55905dc60773cc6cc167e dir.c +100644 blob 2df15defb6720a742282f24721233c4816deceb6 dir.h +100644 blob 1f73f1ea7dfa6a14dedf384c99751e86c8121ff4 dump-cache-tree.c +100644 blob eebc3e95fe0c7e61f7c29fa5412ea9d4a5900f92 editor.c +100644 blob 222aaa374b8268828e9d529a8afacb8830acc281 entry.c +100644 blob 0c6d11f6a0c6fa5dbab2f36c4b4ad4de5aba4ac9 environment.c +100644 blob ce6741eb682b59ad638c7bee6ca31e2fcd53f281 exec_cmd.c +100644 blob 594f961387240c221020c9ea0bccd8a39ff69595 exec_cmd.h +100644 blob 7089e6f9e6c5fa9142f468e54afe7d33a6d2eec7 fast-import.c +100644 blob 8bd9c32561e79d194d27fa10cc98a26aa2cb673c fetch-pack.h +100755 blob 63dfa4c475ae3632fc5cfd093d949a41683a5458 fixup-builtins +100644 blob 797e3178ae279f444d2efa7e3758652ad0898dd7 fsck.c +100644 blob 990ee02335a2e2693e32baa82b259c23843f2aa0 fsck.h +100755 blob a2913c2a2cd1ec158157ada3e2deb666892b734b generate-cmdlist.sh +100755 blob da768ee7acc22e6480f0a067e109239561d43927 git-add--interactive.perl +100755 blob 6aa819280ef99ccbbdeefde037e99fae3e6f0110 git-am.sh +100755 blob 98f3ede566a6cb0c902ce84795f7de8f8afbe633 git-archimport.perl +100755 blob 3cac20db79e1e408a321b0e9d272501985a3c49b git-bisect.sh +100644 blob cf89cdf4598b3796724a85aa707f740245155cdc git-compat-util.h +100755 blob 6d9f0ef0f989133422cf8c0302e63dab15a999d5 git-cvsexportcommit.perl +100755 blob e2664ef01308fd0fb65d47b25e0ae73a65aa6262 git-cvsimport.perl +100755 blob b0a805c688f59af29e1f25b514d73f3991285dee git-cvsserver.perl +100755 blob 182822a24e214fe7e93a2df68fdda3dd40b5896d git-filter-branch.sh +040000 tree 320e89a01513e3630eb681d9524ff835d56f6690 git-gui +100755 blob 0843372b57371b62cd68f2818f634209f55d5395 git-instaweb.sh +100755 blob 9cedaf80ceac1d4100adf3cfb152c76c7f945e4d git-lost-found.sh +100755 blob 645e1147dc886f2b1ca6d2020b44db746b082bf0 git-merge-octopus.sh +100755 blob e1eb9632660146396a0b5f3f2a410d8cb027ff9d git-merge-one-file.sh +100755 blob 93bcfc2f5dce418d00f26257788932d5c738785c git-merge-resolve.sh +100755 blob 94187c306ccb05d977f2bb35e81828130ab49a61 git-mergetool.sh +100755 blob 695a4094bb4230341618bd6f16d0bea9bff2e826 git-parse-remote.sh +100755 blob 75c36100a2f858bcf2663d2b4560654787963175 git-pull.sh +100755 blob cebaee1cc9dfc28d80173583b144a480be2f9bfd git-quiltimport.sh +100755 blob 4e334ba41dad3067394b79c15ebfe610b2d3e178 git-rebase--interactive.sh +100755 blob 412e135c3ae88d76b5bdf3f08083b153da220a95 git-rebase.sh +100755 blob 937c69a74858a8a3c63bb41a23705b579df1b3a3 git-relink.perl +100755 blob 683960b04d6b743e687b2eb640d2b0e00ccfd313 git-repack.sh +100755 blob 073a314c8043e0ff30afde65e012e356ff0d186f git-request-pull.sh +100755 blob d2fd89907688a044ffe0d2520744e00a9b33c942 git-send-email.perl +100755 blob dbdf209ec0e7d6468c199d1905c3e7788a9cd246 git-sh-setup.sh +100755 blob d4609ed66e56dc6021c800d60286bec38615ff39 git-stash.sh +100755 blob b40f876a2ca9fe985cedc622ab28a9f461edc5ab git-submodule.sh +100755 blob cf6dbbc42773fef394c27cd87109b69c3144753c git-svn.perl +100755 blob 384148a59fc492d8fb1d6ea4fc4532aa1e5ffc22 git-web--browse.sh +100644 blob 37b1d76a08ca59f3de54e11890dce962403cf8d3 git.c +100644 blob c6492e5be2763eab81358424ff625a34a5ff2fba git.spec.in +040000 tree 615732133b5e3dcd80f5477f3ca94574e4430957 gitk-git +040000 tree e3fbfd0f5bfe5e8927abb7fe37a59585aef9a405 gitweb +100644 blob e2633f8376eb7b12706dcd4c698e2b3f6be2b433 graph.c +100644 blob eab4e3daba9812293d4e005c3ebe28f9a97744ce graph.h +100644 blob f67d6716ea5f42c3384a7a4cb2eb973b02785fba grep.c +100644 blob d252dd25f81526d9b8663b4d3c9585d69a901397 grep.h +100644 blob 46c06a9552dac5475afc607c3fe2bf00801eb055 hash-object.c +100644 blob 1cd4c9d5c0945994b84bb25edd6e4685cf76b5c5 hash.c +100644 blob 69e33a47b9861df9ac12c354eae180b4f8fea857 hash.h +100644 blob 3cb19628965685ce59a5377b81bef975851996e8 help.c +100644 blob 68052888570af7d09535db8831b8cf3ef2881589 http-push.c +100644 blob 9dc6b27b457a2979a95018679a0b885e6fb62d9a http-walker.c +100644 blob 1108ab4a3101fb4768cad420ccfdb52d87890a18 http.c +100644 blob 905b4629a47789705c13745fd56ce0c91adea41b http.h +100644 blob b35504a8d25594a8d243ae7490733eae5a262712 ident.c +100644 blob 1ec131092109aa3fbed3cd20f10b56a864584a94 imap-send.c +100644 blob 52064befdbbbdf671bd08e369a133d4f1fee03c1 index-pack.c +100644 blob 7f03bd99c5b66afa6cc7fa11a2430301a3042656 interpolate.c +100644 blob 77407e67dca97eb85274c69e2e7469e1d4d40b3b interpolate.h +100644 blob c8b8375e4983794e601ba69a1c217a3c711835e9 list-objects.c +100644 blob 0f41391ecc00eac324ea76de7654781c4fce094e list-objects.h +100644 blob 9837c842a3f215ebee7cbe9690e42e216fb5c76c ll-merge.c +100644 blob 5388422d091ede134d42406291989c49553f7428 ll-merge.h +100644 blob 4023797b00fe21ecbabe3407ef8a12fca0690607 lockfile.c +100644 blob bd8b9e45ab46b8664c8b7016b33bee22f86c9e0d log-tree.c +100644 blob 59ba4c48b7966db34c6345a445ab0b10e235ac83 log-tree.h +100644 blob 88fc6f394684436967002ca477eac1e084537348 mailmap.c +100644 blob 6e48f83cedd13e24d50cddf47f037791ddc5ad4b mailmap.h +100644 blob 0fd6df7d6ed839eaed536bc332312c2688a6bbad match-trees.c +100644 blob 2a939c9dd835a7e7946eb1548e4cf637ae3ca329 merge-file.c +100644 blob 7491c56ad25332fb4aae6a075bf0577a1d800c3b merge-index.c +100644 blob f37630a8ad07709ae106ddde44a34daf6bad8b16 merge-recursive.h +100644 blob 02fc10f7e622ba1c53065e7cf4563ff10af0c41f merge-tree.c +100644 blob 0b34341f711a903d4a12fe96dc6ef63e55fb2f5b mktag.c +100644 blob e0da110a98d3a7376dc78df71fabc10fc5664296 mktree.c +040000 tree 930c4b2743737c3dd8a58309fd66f019f08ab12f mozilla-sha1 +100644 blob 0031d78e8c98a32d61cd0dc0f939a033e24ed890 name-hash.c +100644 blob 50b6528001fe4bafdfe70126dc2078860c3d1969 object.c +100644 blob 036bd66fe9b6591e959e6df51160e636ab1a682e object.h +100644 blob f596bf2db5e0a0065e6856b8caa3ded8a134f74d pack-check.c +100644 blob 25b81a445c8fafe0c00ce30082b7d9a7c22ccf1e pack-redundant.c +100644 blob 848d311c2b2c651dbb14893c260584f00c639357 pack-refs.c +100644 blob 518acfb370ad72be18399a3ac5e8ca17880281c9 pack-refs.h +100644 blob cd300bdff5b524a4d509ba5276e9ef21f443013d pack-revindex.c +100644 blob 36a514a6cf600e7e77794e50998a9d160e30c8e9 pack-revindex.h +100644 blob a8f02699366c87de960d7637e9f69c26c2241693 pack-write.c +100644 blob 76e6aa2aad06545e7c44fc2c1e117ea668356ccf pack.h +100644 blob 6b5c9e44b4ded338ddb344ae454d83a685b7569a pager.c +100644 blob 71a7acf4e22bd12c0919f277410d6ec52dd5efc8 parse-options.c +100644 blob bc317e7512af7a1cc86641a651ae5415d28e71c4 parse-options.h +100644 blob ed9db81fa82c812c9ffa07f5a40540dbb15da0d3 patch-delta.c +100644 blob 9349bc5580456b378d41da7cc2518e4fa9a7e81a patch-id.c +100644 blob 3be5d3165e0009761a0ca69e15e4a9132c6cfaff patch-ids.c +100644 blob c8c7ca110ad34def12a3594a1560b3c3052eb701 patch-ids.h +100644 blob 9df447bd6dcfaddf7f05fe5f0b624700ff1f40d7 path.c +040000 tree 7f5daf8444afe10cabcfff7b46dcf101777301cb perl +100644 blob f5d00863a6234c16db33637d19fefd2014780e87 pkt-line.c +100644 blob 9df653f6f5afe720870658d7093bddbf3e66beaf pkt-line.h +040000 tree 6f6159f0245784352414ff38ffb68bae80f30bd6 ppc +100644 blob 33ef34a4119812674726254fee3f391fb5734fdb pretty.c +100644 blob 55a8687ad15788f8ea5a5beb463d216908f618b2 progress.c +100644 blob 611e4c4d42d8d1164add09f926ad5b2ce088db5e progress.h +100644 blob 6a520855d6c418ecb1384ef9571b122b134af1af quote.c +100644 blob c5eea6f18e2dfabd071b73e6507c34c2b7b5e39f quote.h +100644 blob 3b1c18ff9b9060d0dd437ce89aedb8871c66c54c reachable.c +100644 blob 40751810b64f8bbf9c0a633472a0ef27d23ed1a5 reachable.h +100644 blob 2c03ec3069decb20f7557af4ac7dbe295f2dcf9c read-cache.c +100644 blob d44c19e6b577023dcbaa188a0e67130ff4e5bd9a receive-pack.c +100644 blob f751fdc8d832cae54647c1a70d888e979d324fd8 reflog-walk.c +100644 blob 7ca1438f4d74b652f962c6bdfddd08fe0d75802d reflog-walk.h +100644 blob 39a3b23804d2da715c564459bf320be23d41c1bf refs.c +100644 blob 06ad26055661a9b9e475d0f8a7bd6d1cfb42e792 refs.h +100644 blob f61a3ab399aa6960fb8eba050321cea4a3b05344 remote.c +100644 blob 091b1d041f8a4d255f59bfc001e098e692dbc15c remote.h +100644 blob 323e493dafee46c0d3f95e3c4cd9c4c9b463bbef rerere.c +100644 blob f9b03862fe78b560ee606637be3b1ce972a2cc14 rerere.h +100644 blob 3897fec53170c50921eb1952bc4bdf9c08480638 revision.c +100644 blob f64e8ce7ff999e9fe4a01205ae51775827484ed4 revision.h +100644 blob a3b28a64dc2d1b888b0ba2a135be10fe04651201 run-command.c +100644 blob 5203a9ebb10b14bd06862abafed0ab73d7514a3d run-command.h +100644 blob 8ff1dc35390083c3648c4ee5790f35633d956069 send-pack.h +100644 blob c1c073b2f05a48772a45602cdc711eef6e211695 server-info.c +100644 blob 6cf909463d4ad3681a2f35269db9dc944f4389c2 setup.c +100644 blob da357479cf19aad4bebc64f874c76fdf8566712b sha1-lookup.c +100644 blob 3249a81b3d664afc89c98e6d9dd6b512092a82f9 sha1-lookup.h +100644 blob e281c14f01d37ab7623998c2990914aca49a7a3b sha1_file.c +100644 blob 4fb77f8863ec075de38b84171d3ef039a00cee4c sha1_name.c +100644 blob 4d90eda19efe0a80c1cb39e8897ab3ed5e6fcf56 shallow.c +100644 blob 6a48de05ff80f86050715ef3dab87a48b0a86ac9 shell.c +100644 blob bc02cc29ef0d5f640ab390614def995f30fe4691 shortlog.h +100644 blob 45bb535773fd9c36f73502df9462f7de800009c8 show-index.c +100644 blob b6777812cb92c1c169ee395164d53a0c2e6eceb2 sideband.c +100644 blob a84b6917c7a17b5f8a922540801e98d46aa24431 sideband.h +100644 blob 720737d856b694bc5239f0c18af372959c99e744 strbuf.c +100644 blob eba7ba423a2d3a383ef93f663c95695438269edf strbuf.h +100644 blob ddd83c8c76112cecd5d23668aaca467601855a72 string-list.c +100644 blob 4d6a7051fe5bccf04a0d0c32a90e5cf9c00dba3c string-list.h +100644 blob 5a5e781a15d7d9cb60797958433eca896b31ec85 symlinks.c +040000 tree 40b1d0c852cbaf154abff6e8f5e94537c1184548 t +100644 blob 4470d2bf78e1fbb00d00e487f41daa4373cf48e1 tag.c +100644 blob 7a0cb0070d46ba8c49d71029dc0704188805ea62 tag.h +100644 blob 3467705e9b0e14a0230473186079e83a582e4345 tar.h +040000 tree b9afb0508810e32cc63b582eb84bc72c8d2225cd templates +100644 blob 90da448ebec3e5375b7725ba7f297c1c74199b87 test-chmtime.c +100644 blob 62e8f2387a1cab97ec1c71d1993d082274e17bf5 test-date.c +100644 blob 3d885ff37ee7fc43dec05dd827679d68cee5516b test-delta.c +100644 blob 8cefe6cfed87c8fe0c11d1263dae01639d2bd0f0 test-genrandom.c +100644 blob a3c4688778d9db28c83c9149c9cff1609b69b93f test-match-trees.c +100644 blob 2a79e729a4018ddb2da9ff633f4bf3b102fa8f88 test-parse-options.c +100644 blob a0bcb0e210523124fa977c8bf46667cf25d0335f test-path-utils.c +100644 blob 78d7e983a7a05ba0652132425a66477ef5773304 test-sha1.c +100755 blob 0f0bc5d02f4dcbd67c6d405350e5aaeb39f44bfb test-sha1.sh +100644 blob 55e7e2904eb5f95cedaec2520ddd1d158ee93c7a thread-utils.c +100644 blob cce4b77bd6452e2ec589d8c0dc0e8156352dd67b thread-utils.h +100644 blob 4713f9165c54405d51e81c3e90847120ee907e5d trace.c +100644 blob 6eb65b873afc9dfd457e974b63d88350bb8dc913 transport.c +100644 blob d0b52053fff9bc463438674232bffb6024f3b1fc transport.h +100644 blob bbb126fc46cfb28a0bc92cc0842c0dc72017751d tree-diff.c +100644 blob 02e2aed7737207225f1b96eed774a1b75dd6d8d9 tree-walk.c +100644 blob 42110a465f9a8c91d1bc643dfae7a9b9c32e3719 tree-walk.h +100644 blob 03e782a9cabc0a12ed5baec0ef59c99f19dbc843 tree.c +100644 blob 2ff01a4f839ecc2206fcc1c13fee9d5d202b1128 tree.h +100644 blob bcdc8bbb3b44a43aa43db6035a31478158e070af unpack-file.c +100644 blob cba0aca062f201c5cd5f8799f2190d4a6f06e7c7 unpack-trees.c +100644 blob 94e567265af9a69a30dd5c578439b6444e50004d unpack-trees.h +100644 blob 7e8209ea4b43995737b36bc58db47e7dd6eadb19 update-server-info.c +100644 blob c911e70c9aa47b70dac41b7f4de2f0b4b6c1f948 upload-pack.c +100644 blob a5fc4ec5fae66823266862fa0254474696c220e6 usage.c +100644 blob dc3735364f85273c2a119b994ddb405c09dc395c utf8.c +100644 blob 98cce1b038a908bec51ccd2f7e1c1f648cb429a1 utf8.h +100644 blob 724ba87a7c9ccb16bc506fc3f25710a4b78e3006 var.c +100644 blob 0e68ee6d2e2fb1b866ecec00c5f6446af366a62e walker.c +100644 blob 8a149e11084eeec4501b5b2c5d22e5266f4852e7 walker.h +100644 blob 93562f03eef21b26945d2d9bbdc96818f4de6567 wrapper.c +100644 blob 4c29255df1b637f93ab3d59e0dcab1fa3b40e10b write_or_die.c +100644 blob 7a7ff130a34942506e6068105ac5946c9404bf18 ws.c +100644 blob 889e50f89fc24984f700d14f7033600fa9fdf642 wt-status.c +100644 blob 78add09bd67c727babb61cd1eaa773bcd0c6e55e wt-status.h +100644 blob 61dc5c547019776b971dc89d009f628bbac134fd xdiff-interface.c +100644 blob f7f791d96b9a34ef0f08db4b007c5309b9adc3d6 xdiff-interface.h +040000 tree 88b6f65753131f1f2c0dbceb1f37950e3494833a xdiff diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-3280af9c07ee18a87705ef50b0cc4cd20266cf12.idx b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-3280af9c07ee18a87705ef50b0cc4cd20266cf12.idx new file mode 100644 index 0000000000000000000000000000000000000000..300c0cea48c6c4eda2b870b3631355c2137cebb0 GIT binary patch literal 1296 zcmexg;-AdGz`z8=xBw$if)Wfen|Y98R-n6x!E8Wvw8HE_v#E_afM$;3(J&x74Y2LV z)Q$1iT(-xv{o%d?ezP~d30WlO{o~}bSuv7YXEk0%y}!Bi+NY5F;<3>Ud#7h~`1YT> zpf~%nNr$-eG+Xt8#O{s4dXa}k?k(2XVRih3k&*4s=J(gu+%LX=d`sY>$NY6y^vccL zFK5=6yT6d|<2_P&W-sI8NG8+rIE_yi-_5-HB{Hf?{cO{}3Ii^?mL8a&y=G5Sv+Zu7cmBCoC&;S-%NRy4AT9!? z`vpMy1+YjH0J3?2>?R=nM4az|fajV^sv(TevSnRfJoL#bbLi`ut*5^~(=3sT{r8l` LdIkBjci#j6ga?3q literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-3280af9c07ee18a87705ef50b0cc4cd20266cf12.pack b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-3280af9c07ee18a87705ef50b0cc4cd20266cf12.pack new file mode 100644 index 0000000000000000000000000000000000000000..fca3460ed2d263db153ef11a3e559aa47be9efa1 GIT binary patch literal 562 zcmWG=boORoU|<4bj+wj_bLMu(avd@dV7p)3wYNd_w#iMsjvLl57%x5H=K9t?spSxK8BLij_wrj#O`T2rS%| zUidH}#Dz~P_m$6`*V#8S7O30$?L3y2f3D=Xx!-Gc->7BMhQ+Jj+_2mgvGJNrY1Q>E z`!M4(EkS4a)5PtqUNAR^RLt3ZvSM!F_9Ysop$AQ_o=`fgan!){>Lnvp)w4&gTr#<6 zbJ0Ne>59_cNnJv1%X-D5F2%7O*}gP)y~fA+sX;sDEEi;$xJmWcT1KEPCI$w^hJhZ*0-ygC ze!96v!gK9rDgDLw?#EQU4L(uJFlPtvbn}Wi-e-b6e7zqtF>ux~X%pkRgJF9WHdZg* z5cec;Vs*D^S%j>8VoFLzLPA>7q6xDjVkSi`2@eW9bYaU!C+}OQ8-8rt@>W#!-)0}K z>7PxQ7@WVWFrc_@!{3{K{@t4O`{?)ne9o9JGxophirXp4u;mKxJX5gi3V?27nZf8F Z&i6pTbIm2y5XNWOvMw(k`ecAR?cVd)Q>xLK&lwoxEGFoNl>JhfUA1R!%Q3<5szwWWW_6&w*H-P<|Mh0JXFX?t zskU8&ZZUI=f+^6PO-H9kesumV|42r%vhjZD@^=iH>@!`y8&2M~PjfQ&!G?2Fq;5?8 eX%jmw|NVt;D;2)|H+DQ*d;M_xp&!SeJpcfaylR~Q literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idxV2 b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idxV2 new file mode 100644 index 0000000000000000000000000000000000000000..1d45fa811a1855a9ac71e5237c8aa91e502e8224 GIT binary patch literal 1296 zcmexg;-AdGz`z8=qk#AjU<4{9gh6473o`@dsEJvC=1~i?0?i@^vjO$d1G59|p%>-= z+BG7ucT?;!r_*^m7bHCO{&8(m$M1KZEFoNG7tPK)+Ih42X!CLTZsRZiw>mnnChbt! z^F&^+;^TY=7k)p;U=*4EqsA>|hMe>Cr0|I+URm#sSE^jxbZkfK{{-$Qs#{)7yZ1cy zlxlS5^9i~kWxrHrSM8bGa!fG1s?kE8S$)-h{aqt24*>6K48)T#v2b%%mOHO4anZ)^4)OqwtbqDxeqp+n<8~%>Q9^4X-{K**qkZ& S;_1>JC~?!}g1(aBk0JolEO)B_ literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.pack b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.pack new file mode 100644 index 0000000000000000000000000000000000000000..99f4fdf23241e246452a248e16e06fe61a25caa0 GIT binary patch literal 7811 zcmV-}9(>_YK|@Ob00062000P;4|trNjLQnbFc3xe`HI=OvgFxxK*WVhLBTJWBokY( z57HEWUlG5+%{}LAZY|6}6td2lIAXBqvqUG2)4{Oobn@mCQGZ4(RCuZo9eRgZS(Agx ztW{L#u~R8~N1j6e>5tOoB>)X~D*h?#= zi1RoOwesHw;>=I3oZvD{+q#3EDRF7U8|i^bO^^?GoSn=;3c@fDK+%0pF&D5*n(3qk z5x1@sJi#QLXu&2WM!dctp1{?AtKX_BOxVu@c}~*P6ug8u%_Bv&cJN+C>qeW<_1tv!|)E&+mISFR;q< z`~G`d;MpJhU+M1`5asaVSlSaO6=*E5O!<;x4PW80W#1TkbN6}pX*lWG z6dRRGSK6wuNtP8aHnE%ZsZ1)O0UzQp&p+C9B?tG;Z9HiD;tU>qDheE8>5`ru;cbs4 z{1xymWu>5n8J>#e^X|?9_C@1VDp*8SHTH-Uea-{=Xy#10FFL~dN@`xur+J9Hn zwkm6nR~->X3-DgXF5My}p7r=4kiV{oPdIQv2gHXZxW3(${LKdZNuC@Qr3W?+63Cy7 z9Ljt*BXO-#qTL7PFTqvjkqVdO0InxC13w#yneS9HmZ@Kqu2Lc1rg`m&=So=eT%;s= zUpwEo7j3K=TAS`ncI-M-Dk-Hf}PeWg#Y7@KlJQH;VXLorEc^B}epV~O7+%BoD~HI~D(a}cwkFH`4@7-*{` zc3CXyjmxW^#q)NtPVy%xmQ5>K9_lFe#rF4|aOl2t$!4B8QjS?`>(D=F*9MeBKb05s z@6!47DHjSW(*EQn*|Bz;6&SZHHmovhejda#2{>HUxAtPkno}ko^sye3_+0MFB(wMR z99x@PJqXB6^U6Wxd8?;7DHHtc#E`zV6@6y{&2QfZEA@5hc+=)MWMd?2Iw)y!T6tT0 zXiJ|sk$Tf5%?8qvE!-LyL@N`2)Jb;q#l>clMQ&rMa(Mvh1H(`v@n- z4grVVSs{j5uM~SouqfE2d?piz|L|Lvd?9WyR|6$I`idxG|60;6oBHIy@IiSy;s_v9etO|zh32N|+t%J`;S-Rut}7e(du+OC@&^FH1+9T96ibSsu)or%MB6CHF+k zboov~Aa5co5#f~tUH+;dsg$mXRq|Bd%;cTUE--0N;fvj!E2|`>(BZfEGM%ScTBYiT zXl3E3IV#bZlN=V6{hMN$u1I~E71#K}eND(qH_`m)a=u=y5m}Yx;MLlZ*%^i;d}8() zcIBLgdy1G(sp=oJBgIlVm_vT0<$^#3dVzGcFIL9y?x{0g*|CB38dZzb3*VYZ^RUhU zD$&s4*H8&OvkA(8s6sx7R~3a}$YX+N$cEi9Em6eu0-08B>wmVV@6Mut-{m?%gVXnC z;2-Q<)g+2cFY6RcB_4VHGILKbbB=i4E6$^(d$EME=Jyx2}z9I z=Nli6!e|4W!GT2K*&2hwtHK#(xC>F3K^8D-zVwYK#GU2^-Y+Q%?RZN*T_@N;5Tn&W zjA7za_QMSIXcw1x2?pE56SO0A@ZyYm3m_HflGIk~JSjveJz=AqZWQ1`h!t^F` z(jn4(nLek>nuxN+oE?d5(%>0RGaPhNa6iYPb-N7yMi_DxqGMm3CUk<-1!899VUBN- zC4m{c$Xucnf(3dYF~7NMAv>$4s@5BY8|3BjG4Z{5E@5ES4;XAq@^37c<5PhXir1ty z$xv{DUhBS8*Nte~LeRjfAhg#v|MmND8Q;!^laEt77+=}TyYba%Ho6;6?e*ORKi)r% z#vgk2YBZfqMi&qC6+ig#?rL;Bx*X64MEnnZMo!0|lBy#!8z&GP^HP*w)H85S0RUnW zVhzRKEdgoPS5k`oJj42Ar_go-1Uh$-D_@#6R}vluoO~Q!^o8vw_d?$Z(6a;MdWJDe zLq3Ov=nO-4>6GWI0i{e+O`kX*T12D`XqxdbR+ zGx7EWvEu}WGJt!37JyJFUkO;fW*419GMFUb*&<}o4poP9aj}fb>dL2V@`#{VJ*Wz0 zzs1Uwb$-Blb;<$hxMfcf8?OdG3O7(};#W)r*14jSu6RR|EynY5@#2bTag3W2p7P3JUr~$-ad~oxw#lMDFvrQLuQTt4GhtFYO4&B8k zTQYP67#xBzf)W`&Wm-#9&!dM}WJ$UKS3q=#{O9(Ib6YZrQ2!tS$ZI}Ctd*3qs*Wd= z%#RMA%srKvIF{jT&C-URVG!BohY=9yvJQ&CM+uHi_c2LUK)Ow(OP_|Q8YxMp!{AkM z3|#Gc=V2U~>a`eiNY`X10eCxIB~hGMY}LcinHzW%;V%lhUqfadKr?I3`&M%l-!(Nd zDk0&t}B4;fb2#;zr1Q)fM+NmWrOnK$Bg2*Ac3Nw?{(BR1f#gBy-s95xtZ` z+QvCi5;=u;Q5^r-a49J{YmeZpMM+QxQNc$fx=TV_2>L9gW3q*E*{nhe^coTfp-JV=!aT?(9yhb))p+dy2chC!Fm@;^2}`(7q75yFd9Z(mWB95%UMo#3;Ue9 zmpz}ztF#F3boh1Q*j@cBt%N9KSKei-P;(420ByVk#X1h@jV1{5rAm-@Sl$yS*6~2$ zG=~V`f9kZ95{#_4w^;uS$UW3e_}PsV6}Tp+(WKE=-m^6iPDV{iSODKANCT~RT6ih6 zlOnGO7<$z-dmNXj_Anv{E$*O)y@U0Up3w$g@!keJex^LEl9oVg zgCbc%JZz#ps$+4jx;m+EB6*&GfB=T6qedlh6}=(Rmby&< znKefXDTm+#PCn&WrF%-sB{${>(5zw76sDy(Hp7d(s7*kWa{aw(9H#P#gAoCvsQ$-5 z)mJ7i9pN#_?n>%t00l6yQV76v=N3|0bK=l$#p#W4UtKBbZz!BeZHP)3*->Uk2~<%6 z&Sg~12>&BWZy=E~`Z=-B zMOIT$e}$~qS4D}$N_~2JKEb}N7n+yB|3%b^+hm=lEahmF`|;aQeg6pi)Kdy6!8E|1 z_h;O?opUcW)W9LVMP0GKAw+g0{+em9xOYs$d&?;5H4p`7KqWJ^DJf}y11)|UED)2o z1XrNHjWp=9%yFzMrKZe*a0AN#f_h8AoQMs$v{gk{!C$v zwr!<6u;TJzPDqqICDOKoaz`j5P|&0I8~S3b_4~%l=WPIL(r6u}ymcW|EAT)4$YQA6u-)Xn4O zU{mU8KyCcmDW&aFF4UF5qd8|FhU9eCTEZszjS;{G8qMhRR8B|~f1wehOrVJ;H-4rk zG>B29B&~kuD?m3cGLhr$o}V%$-wYHn&COOuVSAz$%7x}G_YmY)2WTN$ogh|Z0o1M0 zIvFcE&E#Np-dYDT7#%0tie_!t%De}o+VGYoxDMA$RPHTspnU;uGZ^K?@I}oy-9Ck6y&+#52tYnajV}DNsx$E{S(+euWVrVy5nV{{;%t*ta$D`?U_PayWN1#a z>gc!yJKN^?1d3V3sfYPQDZZ3#yRBR;>A{7+A{eR%RerzsA+tA>@g^ZFE4fLxPUmSQ zrTQ#+X}b5CC?1|i)|Nn?!b6&--RSG+&f0^_Si_A@E&P{VQ2XpQWjXi>K# zx=&(NTv#_oa_gr5mRJez zao+nfB1YV;B}NVYKo_i=eNjd2Z1+ktG>lNFdrA=*v~szsOYT>722L~w zYm4e{Y?ExNb5#exdJR^rxy98tT}#pl6+ubJZ1LX($*HwKZ7D_Wn9s47k^QA_M=J^J zTuy+AQzy+LX<#6RJSnbhy}pUtHqo@6 zgDkoH9=)ca|3HWYy@O)DVtd;5Kb%ZTf^?hdnWD}F$jOYTEsGt}gxy;nDTs_a!U+p&a1J>ldmc_KbmWti0aOL{&cP%C&r*nr z;~YOWCuc|<^^$^CQx0Mwe}jC%a8c9epdf|+UAv5sv?RkcNtcUJI!1XCdU=U<#C?&L zi+bbPYm0}QCt2&lUA%lWiE5-M8Tu*Vp{OmfH@T_Znt4+twv}up>1$ zNT$~K%s+|MN7E<19jl;1KU8(6)iW;Fg+V>7)k)zXt#;ZCGF;7;C(pZ^uM^E^=yVZ< zq06U}G<4{vQdg{W46{wJJ0cd)-)ritCMDG;Pa+|HE8`hK`?p+@Cx%)3k>x0p{mn>@ zd7`-vn6-Ev-KI&!U#Nv&au1TG`wmEQOB1wRZWu^T!qbTA&x>3N-ku#N&$TR~oGk%Z z;+M{~h+1!(b}tW6eZMSn5r7WX!^(No%KA0y41qxo@10^nLu;U~bw*00jGr{~66yvu z5{e^U7b#=H*`C$JYN|IG3f_gP`#j+EqDkT$Y;ezF0CRU3tt6`Y>cBm767ko5Xs+$o zDZbaK*FBGw`e=ND+6Q4cloO+poR(&%q${oV&v$KSE6&r!N4Id~V3vryBBx%yL&sqn zz#ctWE~V}u*IVq9qsO;v?x1&$7!AE3u1PP>h$C)}(v#85r0Txfxeg%ZoSZ?x2C0B) z86mEt_PT*vQb^3DIkiY6x<Pll^+$n^-K5V(^@7x2Q;LB%q5@iK74TQn1t(&nH`Q{=W8T!&jyU<}n) zsoqE;gp6w(n*3sTBCj`dSGMz^Avfk~&Wf}3y=M;+32~LtK%GX~6LQ|2J3uGBHjCqs z9cDDz;(1g+?E>mBBo;b^%|);>Obv9l3IcA&^hh1d5p_5?d)v?uChU%}V6REGYi3xA zfb+vEl$#!>=GdJK8D09JAHZXnL5-|~vBJUp3D3fb|Ba_mz4q@q7Q6?lMbo%POeW^K z@xkZ}J;C=jlU8WhM1iIs8N0dP;r!lqui5K4U*VvuXfQHwI36@bM&C?4>otvV+f8a1 zuso>zvZaMa#-EBLW9w$yE1!ef2`qrisb!oO@3%z&{wUn=>D-%GWu{cvp09Q zSHlTU>wb^j`9*s_n9N4QsR13IMpxZKoeZWR`^0`8&2H`e=Cl z7aIQaelncm{03`}K7!&Qz8sA&Zy&CB5cmQsjPGXP6HW#?XLrn+@N8He#9)2%aX110 z##~_G#g_Jo-ZB<<(Ch)103REa(_1+QvHTlh(*Bg$!PiyJD3_}?5_`l zW+8AFYkVAxFIiA~f~4cx$GZoL8aVgu6+K|W1H`DIy&7H*FK44q5HFs@Hq(cXL*@5$ z#tgc>wd3I>=p9TR?Q}T#G`b`{n91;dFarB&MtU-#Rqn>(Ti*9cc+isJC(?n3@hzQp zGW_)c=Q*MYw8r2Aw2hb<$=!S&VRMquzVLhe3chHC{|Gg@vmXbKGMN3S>H#vF$?dLI zKq*@799-NH4=zB@h{*yW#1InE)!^gc!*JR&jS}$II!xZP)BEA&i2euPL-io*Tj2_< z=+_65B0kVX>;U4Vqmdq}$jAms*Tz9du=l=Qp0<1M=^yFE?cJ1=4V%mcmMO;H7ejh_ zG8}_nY)*sA%ZCX}i=Lo2K+W_4#x@$WPz;@yt$8%L3iiZ&w%3Ev?Zf0i$FTDqSV&8< zt~CM)N;W;~u@2bLHMY9EQR&%^)jrxAh~i?1#|BrQM&x;99|K#Mj+7^Nx`OhtA8x_I zO62(?1N=1mPmkQlnACu?QK_)bIR4|qM{>AhfE|r;o-|k^;}U^2E4B#EiiX-b%IJQZ zPU&f|eB%2bzB0%`q)b-(CjLljrx`b;3{*_un)I4d3;{hd=gu)GtxPu-NDOQ)?9gCR zr??qi1#Ozep?nx_#D$V_rJ!oV(c`x(QZG;rVqcs|#% zmE9Jc0r4mfPxA3WYiT<0?M900V3H0&{*Cy_?}v$jhyiZ^)~G`zOV5#etoYi!3=<`+ z1w0l03$N9^Ui~+kMEC_e@DddD8T9-j`_A;Y6NKEaBk%L(c4J4%f_~e}VKPBk9Uu2R zcGI|>%=R7J*i7ra4wTx96mqdVY;xN+!+4d`?v(V|A%5;3XF%MF)ii8PQ$3aPIe;#h z6~x(-aS0C7et-X#fA2rO&u`3te+R{K z*_jccGMaec@VgU8tAUmsjjGWvS6JwMK!ATZzBH4%XJ=faGoIQD?lIgdNr;bSGto=G zNrhM@B0;z;vARSyx z?`{D-ZXaXVe9kJN+F+|4^!wj@b@k;iu)?s3TgM_zOkng z=Z>ly)Ntc=u>QV~_~z45()1#eljEctc;3chevW8?A~409X*_HFnbF4otn zv~`;c|9XW)56hEzbQhT)Awwy4c#e{<4aQwi`RU74Kj$$Po3d81?M2@O%eX3u4Wir*nMmCF{7#jaTHau-aTLgIYa zY~=TToe5hA0TlJXx;Ym5cckZUs5{03gRn*~_j$e5F%_DE@n=m%4V(R)sIfQ6;)^TU z1O83sNa*G_v}Cq}r4{)vJ^Kz&txPli#UzATib7#o^QR9{}L(X}6QuxFZudH{+D^)HA0K_Z}6(D$=0{{R300Bhz VF_X5wDU%DqfXM_U6$l%8F&By literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-546ff360fe3488adb20860ce3436a2d6373d2796.idx b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-546ff360fe3488adb20860ce3436a2d6373d2796.idx new file mode 100755 index 0000000000000000000000000000000000000000..60331335d9bfa0a1830b707a36648bef5c434de9 GIT binary patch literal 1324 zcmexg;-AdGz`z8=$bb>ZAqRuZp#(Dn^^fAA69z26_@W191=>SP%my@{7MLAqEW9kWfyv-_p! zv#wIbI9*p`^SYhiW29dAUv<;y{p*xDuUg<~#xJ>b`&8x`zwBKhx4ihQ?%X{KcsVL< z*YFlFEt$`vabSO-^6v8ETNhV;zc)u{a%i8O%KaTz{TT9gzn`-G^3t@q~%ak20yBn$`^kUD!HJ!YI+u~xG z#9o?rd|dq5SNQJQ6X*6S#cq>&Bkj`oHC|ie7cg9SfZ6FZFqwt|X-8nOwhV~nfyI;o zP;MHK-r4$UqjsNj$nyU7-?sZdXU_VTp1EB`)i!yC7FVXKnZSo5`dp8Uf9ZS%0H$+^ A0RR91 literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-546ff360fe3488adb20860ce3436a2d6373d2796.pack b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-546ff360fe3488adb20860ce3436a2d6373d2796.pack new file mode 100755 index 0000000000000000000000000000000000000000..203c157657b7f48faaaf23b794ce68264f3fd6a8 GIT binary patch literal 1265 zcmV}Sz|tK;P-Gwm?J9d1k8;pF90a<>b;{kj#B zv8`v&y1z7PL^vp`ln_&e-b0}m74Wq-_DtzN{{n1~EsMqpc$^)KK?=e!5CHFa#eTrD zNeXE|#QFw(z&5+KNSc(givHf{VPGyZb$Uorpb&gw$C0N*h>Syso+%**L`QwRWABE9 zMn9W?(rg-V8Fcx{EIW*FgX>avI8yiQwAoqSv~F3tRM!(&YhAS7dJhL|Q)9*gt%ocx z$v~x4B^Cb9zhPJ{V8#h}oE?lo4uUWg1@}G0zksCQwjhZi;u-V+Eq#R;XbJfmy}jsS zlG)6Z@W3DxorTslLO^P5Xrl3Mh^~(8Y+}?&pO8Xpm3&r^_)+c9wUOy3BR1;kPPeIS zboSM=lHD$N`(>R`4Q1(wnU5_SHiizm(ORiK)85mJ7iRJ~7x9AlpML?Z4lWGF33!|x zj9UuAFc1L$bBeuyA)BWGDb_RS0n&7Bkv1u16}`RDkAeA_sl$UZ1Ko+DvN=RML~D}r z(O?cfyWq#sIjvFCama!{8v&el;^l*dgTz5Dn`IOx3Oz*UA3&~v94iHgC zG-wPQR8UHYzCi0?p%*QXN~xTf#{c{aKCmqy#tC?w9gJNLf-n#U-+PL^fTTTvw>7b zb3oTpr=N`2siiyJrnJ*pvS+4;UGSD=o00WtX^5Hgz>b}xldY{ard{diX~qjP$+{*L zMf}gdnE5T|#tC?w9gRT_!XOYu_ncxbAb}1-O^mUgK@Tv%*v7QbG^5emi!NUBHt+u$ zADjfN4JL*_uFuHS<$WKGL+|=%IeE<9ct*aHlF!N@G^z$%oJ>C%d4mCNaGmM~N77F% z&CdL$Wt~~4x}4gB?G6?#BJ4~sMya7d>tW^>&p@SAA>sTV{{lSeEjGppc$^)KK@P$o z5CHGIVm}~ZSy~`5#`*?*z!tVOrY%i_Mt^VhFv(nIO8<~l!JxFz+o1#tC?w9gIN=!Y~j3?|H?3z_OcEvjq|B8}tF2 zWT{A-loCaMZ}c!QmzfeC8Y__Z2S!G*jyNVBLXbXkV9&1ak<{X__vlRhtO`{o(}0W3 z=_iwJFv1P4_p-rJx@T9Lo%JotI%^lpasnc<137XIcIXj}88fsVW_{@ll5?q2HT~yb by>=`Kh3c^@jzd(ZkB9p`zw>FD_Gf9gVWLq? literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-9fb5b411fe6dfa89cc2e6b89d2bd8e5de02b5745.idx b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-9fb5b411fe6dfa89cc2e6b89d2bd8e5de02b5745.idx new file mode 100644 index 0000000000000000000000000000000000000000..58b712f6c58e3b99abd536c17c306ef2c37c5238 GIT binary patch literal 1100 zcmexg;-AdGz`z8=v;>Sm!$uTYL7E1{%NiOM8TQz_04 zFu@&ekGjK&x_2kdF8rZ&Texfa84%Ho*kj+rK@VChH5H&zs*rGkc2PeLbt>003z%9_ S0GiD$Fy{Ma?`mDr9jVq<)ko3* literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-cbdeda40019ae0e6e789088ea0f51f164f489d14.idx b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-cbdeda40019ae0e6e789088ea0f51f164f489d14.idx new file mode 100644 index 0000000000000000000000000000000000000000..a5aed7def26f945bc329429195702b6c5f82444e GIT binary patch literal 1240 zcmexg;-AdGz`z8=LlH0n9X(VqD2$OYGmwuBvjD}hVOF5{D5h^1umQt-L||Q(cO|zM zGgKV9bGy*?cK4G#9KK}{MLmyPaEr_Ga#n2)v}U9D~~ zW#ai1xsB3Wwl8mU{M4|e>CBhT@0-kPc{h9$XyamDB^6LtD7$dd;$mP{Ndso>L%{G~ z3Zy}KoCheTvs+fx_VNdg(i1Ef@{3(2}m589AalN2)2>jzzK3>0|RypS#7k-Qgn;nxs)tqPt0iB zFkobaY@p)$#gqomM$u8;>#lqK)Q@vqsX3Z6-Q`R3Hc7+leACuiht+jnUkn(dYMsYY z9+cboaT)U<|v3Fu4L@tRzfaSvVHEO@V83ZgcIk}_DjXhlQGDI$! z!Sm>kK!>~rmK|a{#QF0j9`n5@-)RW3IybSnB(+Gdq@skOY2$^Kw3h+#ZE4#pO8jS; z6fWm!G&BPnQCyk?aUX-Hk@m(~t1}bbe)deiP!VXPvHSA@0OH(DH@paVoIT1x4uBvG zMA5aJg1DESz>^pkh!r(xORU)745iwN`^1L|0gpF0)Z=doIA$2 zka41VwVIIu5GWK^=H_JPW$TqxlrZSByeqlAn4#j(o!f=Bx4WP0;qWa30EL1Q>o|Cv lOV&>|)GMhd0RRgq0=2sxB|X#d2zAH=&~J0;0D{{I$P-a{H+%p9 literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.idx b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.idx new file mode 100644 index 0000000000000000000000000000000000000000..3ff542377406158d8155129ae66fc0b51cba76b8 GIT binary patch literal 2976 zcmb`Jc{J4f8^^!Kz75k@v(2T3m=NlQD@m6omF-B%6os<8mQ04SPD7UL%A_GBYonq; zjj|LMHQ7Qpl)Z(rCO7>a=hQi!&ad0;oIB^d&gWU)pXc+;U*GqqK+7Wtf}nseKQ0Kq z2RHP1z5^fBSK$8&0f+_v1wx<^{thBg|9_yN{xxEd{}+hEobRy;dOrge^k0yGSn@ka zL0$SYGH|XS`vc^lFApgE2Pndf6_fyFVC`q{aH{!7!S`e!uY{Fe1l`x!JruLWrT z03GPBp!*Z(fp+6JY=V4+Ujh9eU;zEEF@*dJzd`&3BZ#+rgE8bQZ2gy*fNq6tKZz;m zzhyhr{t;BQPKQg5HG-;>vQG8%3oZ)!R4C|{ulH&-id1<^BBuDrrCRo7T&3XDgOT%5 z>1rjEzKaapU&&mm)NmoAVE-LwJjH!0)vyiS-Kxn6$r0i_oF^strFm_uJiTUCoEg8p zY=?N?zVu@_EaGl8)@N(sXJf-O99_lDG-{G+tvMQOtajsGs*J0JaFgVRiBW4Fqrr~~ zlXJ8pj;Ea-I7$Lp1zbe&l87+OwSf0s@m>zQ(PO)KKA zmX00QCn_@TxKS2Kz@g37?974i&R#`IUXI`Hmen>p0jW76LIewbsi@dExAUmG6>KM%~E7ge$6 zRcV{2aD^w6PN%mYT3a+1Fvdu8KXJNHm8CmK!J)Esl!I^p~X8B)IJ{75^pSPUpG(&BvX)#V|T#o2ph!{RR5E>YE;B?eAncAq@ zrKyCv+mYI{QQCvNq#WVKyns7uwqTO1Ry~)1q#IW8Z zt7^&4RNIK(?!Q1~Ap}urX5nNqEm@2tW4enFXG9)nzsq-TJnWNU>x|XZl8g<#*!@72 zT@j>hUmuqdXW>MM0-h{Nq=%We{e)}*uBI@Yd~{cMqt{u>sC@n$;p%3@6iq}nX7m0B>*UD0P>^*$yO|n(wH0X)C4ByPfrK;1XqGs!= zMT~luv^)>-FpA#Aq*YgX?s&iHO}fZA?4EfyQ(tC?AcLL#yRM-dmtntrfW%azSaI(S zy7yhb(s+SrkqD_i>VfsH4hEi8ZR+MKR^W6CW_?5ul{r}&>s)bX1C z<>8;PkLH(OQAudNf9(|cwmDI>c1t8-dZuo-#aZVzi$`@~R|uZbqGJeK-l<^poXuqp zW+$se$Fo>_=!!ivK?0Ehj5B1W#xv|#ycQWC%RmEHP#L-;(#1eZ~CuxcCQ49z0c#>m&{CmRiCt?2< z!L+9fg?)ITS7!1TXc`rxwVxan#L#V4k~TLD6tO)>-0cn1MJ=hv!o6Kl41>Zx0lm%& z;fv@6!}Oy{I&M<>Z=DmN8sGX^rr7+%lGNoeCSvFUz+}Y5C_I{6Ur24~5 zf68R5P49e9@1KNjUkx2*x=EUM(jIAIh;1{s%HolV;^m#O=uDqEeE8Guc64179oK6)EJ+M1yFi^*UPr^Px5bi|KUWUD1!H!LE9|v_icn5alDu(&s zGq9rYKe%8X6*OKjs{-!ag587;F5^ebTI52rd}}9c1m}n%4r?UrrLu2#?VMVl9G{wD MS&Qr8b1^{w4=0|DkpKVy literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.idxV2 b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.idxV2 new file mode 100644 index 0000000000000000000000000000000000000000..ccbf00e34fb8de324c5fe3f584b80bdb99e02df0 GIT binary patch literal 2976 zcmb`Jc{J4PAIHCTgRx}KHdlq1j4d}LNmof_JF>(LG1Yveo{aZFc?Pt&gJr2L`Ws~j~98nzpsR)Iq>AhXc=~_`Kak}JiK`rYFrUEtfaQGrI zQ>~Q1zD&jZnaZI`iWV@A2-$tsPc*1PO}wF}M>RDmHAa-X{aLBX(!7>+zFspk!JN-Q zwo{btnCXoXM|`YD*>;w}wzhPmku_X&<0h$=nj;Y=YPasE%XnD|Hc9?9K4QadJg}@V zIY%xwsdbJreIT_sf#lkIH*B5_tu|Cx(CMO#)hj*gKDOjLxRSBy9t9hzXV$)VS`l-t zZ1kvsg(CGXp0MaTRx(4=Yf=f9N$K^8?7p)vM9k;xFEZDS<{hzm>6|*a28f_fr zdnNw1CX$2UXWK?19Dik@qro^~*ph0#n3&ZbX``c7Gjq?P@uSW5z-Vr!6jNFxNM=xY zETYOjuh6aEl0?O16mj=o5qnwg<;3I2GIy$R5X*S?n@79l=1VHS1la~U1^bHKeO(oQ zQI))93R844c`{qI4rAu8m>p7}d@5GWAb%yxZHDx?=CMgy<4VlSg_t3a{^MaM4xS<2kf|ls zE=?uX-HFwjC29@uxaJBr<`+iD%)dxq&x~cX$Kl7V50UVdkzrOB_5LUniius)j0_FW z32P9=Pz9>rP<~B_k|et3iO0Mg-}?vE)U&nN&X_lJXqZUc6SZ8tc8%MC(?{mZ&T(?Z z&wCO(-kZs_HrFjOAYF(?BU;h(nau1x7HJ$Z8Hee&;1yVSe)MCzQt z*EUQ{m9{I3@GcBJw|;Xlf5-d&cN+<+H?F61JNO3Ho93*O)1V~lQiHMG&Zw^N+x=1hU8c|l@%{67vmkmDipt9QP1gv|Vf0cyRAQ=8q@?c_ zCE#9gSt5U&P>d9tba0cGlc8T#tNMLH?s&Vk(nQ%`^%X+2(+n$DR`&haQ+%%^>Uehj z=^Jw1ksM4bufR4xxN$1-&Mpgd?Y3C#^i178%X99nmTh$>u44V-(4z=T-mP%tg6)-d zu|3RE9lsK-!K)5bgYd}4yc^_BC1Ahg*HuhRWX#B5Z@WP%k*t` z(TI1xIf;C|zUpQ9bE7TuA7r9yt4g--Pau1;MUwm0rr<<)2}b=YDy}ZH#E;maPl6$j zQ5nw{ir6XwZ_VW|ku}OkYCpLshzPe@OWNKxR21(;67FbE7e7w-jt=nRq#72n`SrTW z1uqLP7-b$&(!oph_^B@$^|#hM5fj{L(XRQ>UP%ubRZ;i{KkcJLD(=;O>WxupVlwC8|y2ZogB`bU4g|hyyf(73_9lfhV3^|Wc5k<P z`-VjjF7PqvO0ZlxkP{I^>Hx%cU>jLrwP=vHAqWrbs~%7o{#xNQu!^APy^kOg5m0`D zAYvrYfPa(%A1MT9`IEuY9s~_|6Nx?W<^r6{gZn;$wgfD!CD_y%=+(nph0p^JC3zUE zS~koX3-dex9U5?lGw_c~g0B<=jaV;W54shY`xa;)!CY_Q-A|x#fO;a_1+#I$8+^`? z!=H-+4~2$YssQeqfHRXYoBYl^A8q!6_j1OT;sSk?0(z_H4h!AoN{ZqUN0dGrKk#Uu L#~L@=K-~WTG^vnn literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.pack b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.pack new file mode 100644 index 0000000000000000000000000000000000000000..ef56d7e941828561e9f6e2cbc46e2d6c9c8d76bf GIT binary patch literal 5901 zcmbVQcQl;ayZ#14FhN8oqa1_iMhiwCUGyM2LG)-*l4waJI?+e(iI(U>^cqn@^if0f z8X`oApK|Xx?map8WZgezt=Vhswclqy<$d@1)Fh>q006)^`5_}oJ(7~UBvo3tA>dvw z)fa{WYOPK)jKorofSV&Q=0w4Wri=+|`MShDkFi=;H+g(uInUD4z*4@TA|I=%83X0Z zY?TVMA(jmAtlqR-S$|I#L(?pYFF|4Rhe9xgw3J4Xwvu_s*L68bYZM+N1Syd$&p;@- zB>gS`{gvVRgcl(Ym|X%j(&eUr&ozPcJF;yor`1hI$;K5#gWSGiZ=l@JxGBRU$sy-b_5G5HmpEGT{ideUs- zkZ|SXdLc?Sm^?(tU16Dxnh(7kq25GN&c3TNm}Pn#V-YAGDQw!QS%0qSTcqI+_^Vm7 z=8L(12;W^WWMC#9$mquH!_y7|3*{%Uxf|kW$#uIl2;b@b@+yN6Ry<<(It4)=RM=hj zJw29PTPb+*u9m%!uri6^h>N%-9!lM^xzcG;_^{#1&y=~Smt)_MQx6vA_TNO=b&?ak z=r&(ioW}ekV|2-xQhG|Q`_1<#sim3^lmw4_JB0;U=0W<_>Lg#lz7ifuiLI{T>Xlh6 z{I}?ZS{bhkag4r4!1}saM%g=>TIgVd;|+Az2f0VP==uQIQ+<#fl=ZG?gu_I00b+^4?bfUdnCPU_lR?n;>!HoBF3gJeFtoa z>4J&L`8pz`D}%%zd4!ba1d0&2DrZy%OWK?`MP^sX->uXpBD>f^`uZBE+LqAdy7S#M z1?*WrXgx}XOKmUs<7{o;1W19cziG(;fsJI$j4)$K(o)OP4lEpmPuT3*OVF9i7_mP$ z&q+W&rM9pTNr=!yu;tB(8|yKBFkt29iY#(ck;};$$GdHp(mZoB=K*b8Z8P!kliDxynr#D=y^Yc+UcS5f* zuwKC$u%q9h$cPz`ywY9pKCndJygc_ojOf6PXix{-Jt7`+UFeETRz@#=k#AI?*utRk zQS+3KW2B|6_p%u+Wn3k{HT0v{(_UvQ99bV6&AC&9@C(%9S{Y}+ZGKj)O?D8x*oUU zb`KpcX3&Oo>L>Dr-z^!cwldsIAsFXPG;G%?+;6vWNSME_Da>SBcTg@qc&@jSLo+u7 zxhn>CzdcBlO9)G2L1VfO)A&X&GD_IvJjVF8cV9qMLXh{#bHU6+NtI}`9iw8FTpk(A?@9WaPGc+ljG?Ir#0mNuwGg z??P7l`!tlfNu8c^g*#1%JZhk7ArJ{lyz#qag4k=UQsNa>JMrz%L0K=#!F6JWD6xEW@u@1!0&@Z)RL;zD>2~zBwE{g`Z(%W;R@(e3 zM67#u%}2Ak$|*_wc>GgfH%|qZ`0S;^gVTE>ijN0;Wx@kPRKlA{*D8@%b91jk$8mRW zy8Ht~8w@X2?U1|6w?ITxx}>l)Ud5S)YZZmBw^v@PFpf&7>Tr8Cqd8OO=Q41e?P@v#xqW5otr*qpKHv6tzJGoBSyq=p>WrC7NsP&ZiL zwLDjfsLiNWb(BScM16*0%*tQQpCk4?FT-In6+rvjyG0Iflu@6s;$bI-HkFSm2ewZL+-BMRPWK z+0i_;q!yx*1zpA0Gb5X5qkarQkET1%dEIR*^YgtuJUy3!slz~gl>Y3 zp>N^2Rkq2pe$MPx-uFu-a?b6feookKN(Q+jll5I;qCRrfG#vSA2u@EZ+OK`su(2|g z@TXqaqYTN88&1;|nqfi=M3;PD#k&eQ5|i0YFti#CzU=J>c+%#@+)>X`;3Z6o6i?8N z>;yKrNd-GR3y3uy772(RA{Rs$F=J0?4dKm(59LTh{aA-N2Hve7NoMG*H z%s4sBSi=4s*q?TKfv?LOrR*(%6iksIjId^6(bYG@gBm{syN44R-D615grv*rPXr`& zTPX#`6hFwZF%S^a35ZV8bB@v(Cb^yTvPBt0Cv-gwK{h+0!Mb?m5$s#W7J9>_|E^d6 zFb9FB?(D!{=Pu;<}zDdLF6iF`-$(Z=&|7vXv z)J5}RSBd)gU*YfdtEd%`E8=k)zXMMZ&?Z#`E$SL#*)VcpMM-~%MnzmB{cZ#)-Jw@k z!`DV7KyNgHFbzVbyo@!=V@IR);ELRm6kXR@?ACY?DUm`MSK&3~3pMC`uaN^N^LmBD%V2L0gGqFZCqf@+fCP+=JyGgGrG~vZ@z~&a zS(j${BXe%!iUmoWGbzK)%nuy91Q|3W*!3eMkUj`Ms7cgB>*I1*&h%INH`7zx+}wrm z6?v#6&}#m^Ly7R&`zPkA*Kc!Sae_0l$O;Y}sFbD~dPu2f8zK#XtVYuSQLHU7B_{)2 zlGkfOI$nc>UQ?f+PN3J8n~alU$=xkddeD0K9T?wg#o|pu(5rkEZ9ZAc0z)4wJdkSQ z{fv@osi*I#unX^~t={FK_RI7wIPW=ye8mb!rcpdW5Fzg`GMo{HUtj4AEank?2#nHF zW|5IDM@rcxYroE@0BgnOP3U!f1nRTSwyr{1ex47S^ z)FYpkz+XwqGRW2TI{QDclODNwN7e zDaOb$ewr%0E7f1FUP?5$a=nry582aQ>|TfD-)4C8y0`@r1}A~U(mX}Nm|8{*LmL#CNgk35o6;nCIz8=wf6J)&Pky6shJSoq{z_$@c*yCF08k|4xQmwq_ymNFV(@i1_$6lYgft6(~}I^I-<|N%iyn z@_goRTc_ML^Ndqd3(@;^33_&2ktwvo&$w^bMg|dM_Zf4gf=fok_Sg>AZQEH7j?{03 zwVSYpG_dA?nekSZg58F?>fH>yriL`0Y&IJL1t~B=UXLUtzAqR6C=y5(IQy@7y2RQ8 z37sux6!~UOmd7BGEUdqPzz5Dii@4j_+BZ+Zx*+wQ{j8dCP%)a<_b%Ns#nPsCWHYmQ z_Ly7RvSC}laS|*kNnMZ%0(4N6b*IO|zbL{_s(?Wtk+bEDBK)KZ_!3C;Vike2DiQVG z}I* zg2itc%jMr>Wjsx{p7oC{ivxYNw;p3tJiLn{;asrB0)D1b>iuP!XMxwQ{4Pkc+0qLrICWG%NZ!?JJ+KTDPI;z-nr@{~jdMw3V)9ObBx6FIjIdiOv+duod`-2`c$@U{3h<$mssHcD zbp=r{1lvZ@teu7Rnvho;{?^OmL;LfTeBZv@T+I!D{$It0}<8L~VA4 z{4|&GKx)B>ThDxH{><8NQYgP#I*z}~W16zw-DqhG->DvmQiRl9*fc-l-xRuQv`@n6 zs4Vo3btJZMuR{|E5y4N*!1yb}=e+(B-DRL`05bO*g#FsWCFy>CsK7ue2Qw!Hn-@k3 zGPu(HZTKA4T$nRspDJ{s_ddd}8}#&F0v-x59{g67g-!ClS9SV4-#;;%(RMAGLPVWp z+kQ8Wk9%Qk7iIa^=JnO?_?Y6hcU>tcLsGxs%hP+@u4_D4fcZG(!&6yKTNNk{Nd2>2 zFg{}w5&V#9@_xUDe!f2z>9rTZDa zQ`^JDrx_^JaYjteZbF)QraD%xa@B@QdC z-aGk7=e3b8`V1S}l8FhgDzFunEAs$G3WMCF<|@mog#_GF8Z%O#5%0`$6jJ>IC?ca- zxoFRGm43;>C7{RRGWyTL$X_?2-!6E&Vmu9Y>9OkM7{Je-tfn2e>t5$cECmb zo4qTfi3azgl>gd#XG}Cj52Rm67p%LN1m(!MlCY0f-^2kzE>oZNuG85IiUv~t!iU|n z+n(BElag1Qvp;kQ#&c(a=T-9mqPJ2$vKH`Ypb`l`Sj?E4Bc57U(H;c>COy;-{|@li zuL?C%Srby6#N|=iAR4p#^-;I@I5IL#I)?0pfdeo_k?_S4iUC};#dQ6D!2IP1u=Szl z{Ohb|%*R#1DlRHjM_zLr{I7={;4UI%l~jM1q>*}YfeR*s2?p=T zIU6wk_4B&b>p82Ej#P`PPxa%+_wi6x%r9HEzigk^72!*R(HE~XmdBwohOFcPlZe`fOAIO_p7OQ7aLow-ur6BrQ9a~gP-LdYFlbTk|ir2ci(vyca zWTN?cj^w~N8it59*48Z8)HR7-*+!=MCh3w_XJh;M zHspEnByE3>7bK+hH=$JGE`=%9C{C1Bw5D?;X@Rqnm_24(7sd+iMRX{Ui?H}x`(D;& zO;{~|=@}wj^I7A0_?~auCzFKNl6IaGlK7naA2P8&EZG&8vlk3zyP^#P1YGzGC4rTL z;I0~mIKm5lr*HO@J`lZt=}tZJz%;8BXOxp3!|2eBy#c)z98~mBacJF%CT(698QftV zAg4Z#hEpCF6myiwo#TMW=LDAurpc(c5$AyotK=j+lEY;kbnFSMJx&Tdj^N@t2QrtR zh1D;_4Q)-PWD69NGMhJQg{8DH!5sJEH4evIYs1H{<@I|PvPpj7) zDo|Ly=W~L*cka0)UJbu1P?Twx$C04wXTGLwtsp!6Y$Ek`u(5yI(UdtCYd#z;YuB0; zZ}{tIL!QYxaZl*5nVrFXVd`pblMJYMm2(=&*)h!f@Gd%7Di#Uo{K4_EXL5x!({j^Z z*eWV=%QP7EF^`59HK?K1ea&*Uml04z7$tfh@9{M>XLZ%13W)N>;GeY|Kmu-odXy`L zXNwCq^CII+OQ?}cNWut)N905GR}TomdawmHVrOfx#6_NCr}L_QBt6DsR^F@e zQ`W_#)tHz#9~ME~K6(+@=o`5#`^QJ`hSwYD{>_-SWFiNtM!>G=V9@$U%j_m=AqZ+q z=p@#PgL#?MxwBC3ykA9Re|;O!7Fs^IZgbN(ENp^#R=HR&J?30pmox72<(7TNvWAdS z&ReERakjp|?vneYAvV^UPCB`_EmuZZjRgzhJ7r3SjZeCkZ=LBW^uy;LQ!D}^xU73O zLBpXR;kZaqr6N3E{z6p!gDClpr~$yJLP@-Ln*5|c?~L347@D!|_$a_bak^7z!3AYB za&mx-9$bMYh#yw{oH)zDS3|iE?s!jjTcC`q9Y22}KP}3~k>4u~P4Ryw{67hYUn=})kfsm@LmIO-Ehm5A7>h=<0FpjgtM#f3Rv=2XHs;Qc0 z$@ckt|Ns4TlI&@zp{_zVstFDKpzz5!2&-xB1*K2CsvLW@ANX!Csj3OmT|{^xH;JzqY7Sj#YMn32QvE&x9@dd-)~1Mb`PF6xt?5` zpe3}N380K?$80d79Y0eCq0NrBwm-;$yp%#0T(qEf6nX>0ap0x(S;DEM#tm6F1J?o< zw(zgBqK$01ma6N`r4^X8AvjaT>uyjjEf1}rk~ateT!GQzve3BZhhZN7&H{dei2!4M zY^2f%8OtH;{kbsDPM%cHskmTYpb%A$CO$M65O6=~D|2kM+!DIASxzg2`!!F};IKg)voiI^>d^-&-5%Yw{*l?2sco7BwR>mZZyTM+HZNT#Fc p4&CM7pY{V?vWox5_6J`K$B>RdEJzhJ?}sx-{a@bp3z_o;V=-Tij`pRd#nQY7F!MKA3*`kQJkj)a;Y_-2T@r>6|DcOwP40# zYM}m+Fks%gZ=ep&EWm>Gx4?mO3pBx6>t9&M^kUw-u`-=-wd|mdRKhc%z3Bd_j3ZUc zWckHw-5QZ>PpzD%uTgKTG1vcev~<^Nuom1z?blMx=#@?B>gP17cQlPL4MPkyd@=~+ z)|HBnlU&683U;z@2B$a;m(qG$p;5cvhO{_Zg2(gPZVs&H8s( z)cEQS)2k&4x3`wT3Gp6Ioh2V$esJzoU&X+SjMv(5nvQk5RCYO-7~DA<{qUL_r=h{h z1XoRW6><+q1Zj+SI)zT5GZWpPCdAa9ada={A2yuYT_;EL_G#TZO0X3TbEK`si)C#x zFPtzCHmm2Id6gu_a3jqUev!BEtrs3fUOe@#F`q+Z+Zl_Om!Wm6jhS~a10Ki>>ivbmIqh$0`iA9Exi zH;hv_7GB)wA@R(Kt16Usrp1y_+*O0hc>A}{9Efccj-HOy2nHle%=!;`GTj2jo>23 zQ2u%~`v`ddB5aqX@_@VNknzl@6$F{h20Xn(c z)%1rrQTfE_N#7wKNt?zS_7&b8I9BFBjwwJgnVBQaz*|R& zInieX+@zRYX5-~`UN%&(>5`ODHJOLD$~YobkVMZLE1j0+kr=IEbq=I(Q|f`5Q1C|v z;EM3clORO^Q2@NhBF|7>-*-)LBJeMpcl=)J(n&)cvLceIf4uG;b?o!TN zo!C9!zXRpjn2H%FpUPah2jvJWfjFO^=k%hJ)8=Yc1(YSsoT~Z#?$l*jF@l0Nla?U^ i5q#0RPo(gxfy(+nDH~nU#sqlR)DGh}_SPGh&i(_^HDsp% literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/packed-refs b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/packed-refs new file mode 100644 index 000000000..82fb0d7b3 --- /dev/null +++ b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/packed-refs @@ -0,0 +1,36 @@ +# pack-refs with: peeled +6db9c2ebf75590eef973081736730a9ea169a0c4 refs/heads/a +7f822839a2fe9760f386cbbbcb3f92c5fe81def7 refs/heads/b +6e1475206e57110fcef4b92320436c1e9872a322 refs/heads/c +f73b95671f326616d66b2afb3bdfcdbbce110b44 refs/heads/d +d0114ab8ac326bab30e3a657a0397578c5a1af88 refs/heads/e +47d3697c3747e8184e0dc479ccbd01e359023577 refs/heads/f +175d5b80bd9768884d8fced02e9bd33488174396 refs/heads/g +175d5b80bd9768884d8fced02e9bd33488174396 refs/heads/prefix/a +68cb1f232964f3cd698afc1dafe583937203c587 refs/heads/gitlink +49322bb17d3acc9146f98c97d078513228bbf3c0 refs/heads/master +d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864 refs/heads/pa +5ce00008cf3fb8f194f52742020bd40d78f3f1b3 refs/heads/symlink +6db9c2ebf75590eef973081736730a9ea169a0c4 refs/tags/A +17768080a2318cd89bba4c8b87834401e2095703 refs/tags/B +^d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864 +032c063ce34486359e3ee3d4f9e5c225b9e1a4c2 refs/tags/B10th +^d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864 +214cae792433672d28b3aeb9f75c1ae84fd54628 refs/tags/B2nd +^d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864 +1170b77a48d3ea2d58b043648b1ec63d606e3efa refs/tags/B3rd +^d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864 +8dfd42699e7b10e568fa1eaebe249e33e98da81e refs/tags/B4th +^d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864 +efee904c794b943a06931c76c576dd552212e8bc refs/tags/B5th +^d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864 +d54e006ebbef94b7d3a5cd56d154f1e6f08efb94 refs/tags/B6th +^d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864 +a773cd2d9dbca00d08793dac0d7002a49f0428c0 refs/tags/B7th +^d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864 +bf5123bb77c7b5a379f7de9c1293558e3e24dfb8 refs/tags/B8th +^d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864 +dd144af286452bfd6a1ea02b0d3745bcdb555e9d refs/tags/B9th +^d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864 +8bbde7aacf771a9afb6992434f1ae413e010c6d8 refs/tags/spearce-gpg-pub +^fd608fbe625a2b456d9f15c2b1dc41f252057dd7 diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterReflowTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterReflowTest.java new file mode 100644 index 000000000..d9e50d20a --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterReflowTest.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2009, Johannes E. Schindelin + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; + +import org.eclipse.jgit.patch.FileHeader; +import org.eclipse.jgit.patch.Patch; +import org.eclipse.jgit.util.RawParseUtils; + +public class DiffFormatterReflowTest extends TestCase { + private RawText a; + + private RawText b; + + private FileHeader file; + + private ByteArrayOutputStream out; + + private DiffFormatter fmt; + + protected void setUp() throws Exception { + super.setUp(); + out = new ByteArrayOutputStream(); + fmt = new DiffFormatter(); + } + + public void testNegativeContextFails() throws IOException { + init("X"); + try { + fmt.setContext(-1); + fail("accepted negative context"); + } catch (IllegalArgumentException e) { + // pass + } + } + + public void testContext0() throws IOException { + init("X"); + fmt.setContext(0); + assertFormatted(); + } + + public void testContext1() throws IOException { + init("X"); + fmt.setContext(1); + assertFormatted(); + } + + public void testContext3() throws IOException { + init("X"); + fmt.setContext(3); + assertFormatted(); + } + + public void testContext5() throws IOException { + init("X"); + fmt.setContext(5); + assertFormatted(); + } + + public void testContext10() throws IOException { + init("X"); + fmt.setContext(10); + assertFormatted(); + } + + public void testContext100() throws IOException { + init("X"); + fmt.setContext(100); + assertFormatted(); + } + + public void testEmpty1() throws IOException { + init("E"); + assertFormatted("E.patch"); + } + + public void testNoNewLine1() throws IOException { + init("Y"); + assertFormatted("Y.patch"); + } + + public void testNoNewLine2() throws IOException { + init("Z"); + assertFormatted("Z.patch"); + } + + private void init(final String name) throws IOException { + a = new RawText(readFile(name + "_PreImage")); + b = new RawText(readFile(name + "_PostImage")); + file = parseTestPatchFile(name + ".patch").getFiles().get(0); + } + + private void assertFormatted() throws IOException { + assertFormatted(getName() + ".out"); + } + + private void assertFormatted(final String name) throws IOException { + fmt.format(out, file, a, b); + final String exp = RawParseUtils.decode(readFile(name)); + assertEquals(exp, RawParseUtils.decode(out.toByteArray())); + } + + private byte[] readFile(final String patchFile) throws IOException { + final InputStream in = getClass().getResourceAsStream(patchFile); + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } + try { + final byte[] buf = new byte[1024]; + final ByteArrayOutputStream temp = new ByteArrayOutputStream(); + int n; + while ((n = in.read(buf)) > 0) + temp.write(buf, 0, n); + return temp.toByteArray(); + } finally { + in.close(); + } + } + + private Patch parseTestPatchFile(final String patchFile) throws IOException { + final InputStream in = getClass().getResourceAsStream(patchFile); + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } + try { + final Patch p = new Patch(); + p.parse(in); + return p; + } finally { + in.close(); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditListTest.java new file mode 100644 index 000000000..5c2c3d0a2 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditListTest.java @@ -0,0 +1,127 @@ +/* + * 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.Iterator; + +import junit.framework.TestCase; + +public class EditListTest extends TestCase { + public void testEmpty() { + final EditList l = new EditList(); + assertEquals(0, l.size()); + assertTrue(l.isEmpty()); + assertEquals("EditList[]", l.toString()); + + assertTrue(l.equals(l)); + assertTrue(l.equals(new EditList())); + assertFalse(l.equals("")); + assertEquals(l.hashCode(), new EditList().hashCode()); + } + + public void testAddOne() { + final Edit e = new Edit(1, 2, 1, 1); + final EditList l = new EditList(); + l.add(e); + assertEquals(1, l.size()); + assertFalse(l.isEmpty()); + assertSame(e, l.get(0)); + assertSame(e, l.iterator().next()); + + assertTrue(l.equals(l)); + assertFalse(l.equals(new EditList())); + + final EditList l2 = new EditList(); + l2.add(e); + assertTrue(l.equals(l2)); + assertTrue(l2.equals(l)); + assertEquals(l.hashCode(), l2.hashCode()); + } + + public void testAddTwo() { + final Edit e1 = new Edit(1, 2, 1, 1); + final Edit e2 = new Edit(8, 8, 8, 12); + final EditList l = new EditList(); + l.add(e1); + l.add(e2); + assertEquals(2, l.size()); + assertSame(e1, l.get(0)); + assertSame(e2, l.get(1)); + + final Iterator i = l.iterator(); + assertSame(e1, i.next()); + assertSame(e2, i.next()); + + assertTrue(l.equals(l)); + assertFalse(l.equals(new EditList())); + + final EditList l2 = new EditList(); + l2.add(e1); + l2.add(e2); + assertTrue(l.equals(l2)); + assertTrue(l2.equals(l)); + assertEquals(l.hashCode(), l2.hashCode()); + } + + public void testSet() { + final Edit e1 = new Edit(1, 2, 1, 1); + final Edit e2 = new Edit(3, 4, 3, 3); + final EditList l = new EditList(); + l.add(e1); + assertSame(e1, l.get(0)); + assertSame(e1, l.set(0, e2)); + assertSame(e2, l.get(0)); + } + + public void testRemove() { + final Edit e1 = new Edit(1, 2, 1, 1); + final Edit e2 = new Edit(8, 8, 8, 12); + final EditList l = new EditList(); + l.add(e1); + l.add(e2); + l.remove(e1); + assertEquals(1, l.size()); + assertSame(e2, l.get(0)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditTest.java new file mode 100644 index 000000000..6f3d21e55 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/EditTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2009, Johannes E. Schindelin + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 junit.framework.TestCase; + +public class EditTest extends TestCase { + public void testCreate() { + final Edit e = new Edit(1, 2, 3, 4); + assertEquals(1, e.getBeginA()); + assertEquals(2, e.getEndA()); + assertEquals(3, e.getBeginB()); + assertEquals(4, e.getEndB()); + } + + public void testCreateEmpty() { + final Edit e = new Edit(1, 3); + assertEquals(1, e.getBeginA()); + assertEquals(1, e.getEndA()); + assertEquals(3, e.getBeginB()); + assertEquals(3, e.getEndB()); + } + + public void testSwap() { + final Edit e = new Edit(1, 2, 3, 4); + e.swap(); + assertEquals(3, e.getBeginA()); + assertEquals(4, e.getEndA()); + assertEquals(1, e.getBeginB()); + assertEquals(2, e.getEndB()); + } + + public void testType_Insert() { + final Edit e = new Edit(1, 1, 1, 2); + assertSame(Edit.Type.INSERT, e.getType()); + } + + public void testType_Delete() { + final Edit e = new Edit(1, 2, 1, 1); + assertSame(Edit.Type.DELETE, e.getType()); + } + + public void testType_Replace() { + final Edit e = new Edit(1, 2, 1, 4); + assertSame(Edit.Type.REPLACE, e.getType()); + } + + public void testType_Empty() { + assertSame(Edit.Type.EMPTY, new Edit(1, 1, 2, 2).getType()); + assertSame(Edit.Type.EMPTY, new Edit(1, 2).getType()); + } + + public void testToString() { + final Edit e = new Edit(1, 2, 1, 4); + assertEquals("REPLACE(1-2,1-4)", e.toString()); + } + + public void testEquals1() { + final Edit e1 = new Edit(1, 2, 3, 4); + final Edit e2 = new Edit(1, 2, 3, 4); + + assertTrue(e1.equals(e1)); + assertTrue(e1.equals(e2)); + assertTrue(e2.equals(e1)); + assertEquals(e1.hashCode(), e2.hashCode()); + assertFalse(e1.equals("")); + } + + public void testNotEquals1() { + assertFalse(new Edit(1, 2, 3, 4).equals(new Edit(0, 2, 3, 4))); + } + + public void testNotEquals2() { + assertFalse(new Edit(1, 2, 3, 4).equals(new Edit(1, 0, 3, 4))); + } + + public void testNotEquals3() { + assertFalse(new Edit(1, 2, 3, 4).equals(new Edit(1, 2, 0, 4))); + } + + public void testNotEquals4() { + assertFalse(new Edit(1, 2, 3, 4).equals(new Edit(1, 2, 3, 0))); + } + + public void testExtendA() { + final Edit e = new Edit(1, 2, 1, 1); + + e.extendA(); + assertEquals(new Edit(1, 3, 1, 1), e); + + e.extendA(); + assertEquals(new Edit(1, 4, 1, 1), e); + } + + public void testExtendB() { + final Edit e = new Edit(1, 2, 1, 1); + + e.extendB(); + assertEquals(new Edit(1, 2, 1, 2), e); + + e.extendB(); + assertEquals(new Edit(1, 2, 1, 3), e); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextTest.java new file mode 100644 index 000000000..5cb8bc394 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RawTextTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2009, Johannes E. Schindelin + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.ByteArrayOutputStream; +import java.io.IOException; + +import junit.framework.TestCase; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.util.RawParseUtils; + +public class RawTextTest extends TestCase { + public void testEmpty() { + final RawText r = new RawText(new byte[0]); + assertEquals(0, r.size()); + } + + public void testEquals() { + final RawText a = new RawText(Constants.encodeASCII("foo-a\nfoo-b\n")); + final RawText b = new RawText(Constants.encodeASCII("foo-b\nfoo-c\n")); + + assertEquals(2, a.size()); + assertEquals(2, b.size()); + + // foo-a != foo-b + assertFalse(a.equals(0, b, 0)); + assertFalse(b.equals(0, a, 0)); + + // foo-b == foo-b + assertTrue(a.equals(1, b, 0)); + assertTrue(b.equals(0, a, 1)); + } + + public void testWriteLine1() throws IOException { + final RawText a = new RawText(Constants.encodeASCII("foo-a\nfoo-b\n")); + final ByteArrayOutputStream o = new ByteArrayOutputStream(); + a.writeLine(o, 0); + final byte[] r = o.toByteArray(); + assertEquals("foo-a", RawParseUtils.decode(r)); + } + + public void testWriteLine2() throws IOException { + final RawText a = new RawText(Constants.encodeASCII("foo-a\nfoo-b")); + final ByteArrayOutputStream o = new ByteArrayOutputStream(); + a.writeLine(o, 1); + final byte[] r = o.toByteArray(); + assertEquals("foo-b", RawParseUtils.decode(r)); + } + + public void testWriteLine3() throws IOException { + final RawText a = new RawText(Constants.encodeASCII("a\n\nb\n")); + final ByteArrayOutputStream o = new ByteArrayOutputStream(); + a.writeLine(o, 1); + final byte[] r = o.toByteArray(); + assertEquals("", RawParseUtils.decode(r)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java new file mode 100644 index 000000000..cc37c2bf8 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBasicTest.java @@ -0,0 +1,197 @@ +/* + * 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.dircache; + +import java.io.File; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.RepositoryTestCase; + +public class DirCacheBasicTest extends RepositoryTestCase { + public void testReadMissing_RealIndex() throws Exception { + final File idx = new File(db.getDirectory(), "index"); + assertFalse(idx.exists()); + + final DirCache dc = DirCache.read(db); + assertNotNull(dc); + assertEquals(0, dc.getEntryCount()); + } + + public void testReadMissing_TempIndex() throws Exception { + final File idx = new File(db.getDirectory(), "tmp_index"); + assertFalse(idx.exists()); + + final DirCache dc = DirCache.read(idx); + assertNotNull(dc); + assertEquals(0, dc.getEntryCount()); + } + + public void testLockMissing_RealIndex() throws Exception { + final File idx = new File(db.getDirectory(), "index"); + final File lck = new File(db.getDirectory(), "index.lock"); + assertFalse(idx.exists()); + assertFalse(lck.exists()); + + final DirCache dc = DirCache.lock(db); + assertNotNull(dc); + assertFalse(idx.exists()); + assertTrue(lck.exists()); + assertEquals(0, dc.getEntryCount()); + + dc.unlock(); + assertFalse(idx.exists()); + assertFalse(lck.exists()); + } + + public void testLockMissing_TempIndex() throws Exception { + final File idx = new File(db.getDirectory(), "tmp_index"); + final File lck = new File(db.getDirectory(), "tmp_index.lock"); + assertFalse(idx.exists()); + assertFalse(lck.exists()); + + final DirCache dc = DirCache.lock(idx); + assertNotNull(dc); + assertFalse(idx.exists()); + assertTrue(lck.exists()); + assertEquals(0, dc.getEntryCount()); + + dc.unlock(); + assertFalse(idx.exists()); + assertFalse(lck.exists()); + } + + public void testWriteEmptyUnlock_RealIndex() throws Exception { + final File idx = new File(db.getDirectory(), "index"); + final File lck = new File(db.getDirectory(), "index.lock"); + assertFalse(idx.exists()); + assertFalse(lck.exists()); + + final DirCache dc = DirCache.lock(db); + assertEquals(0, lck.length()); + dc.write(); + assertEquals(12 + 20, lck.length()); + + dc.unlock(); + assertFalse(idx.exists()); + assertFalse(lck.exists()); + } + + public void testWriteEmptyCommit_RealIndex() throws Exception { + final File idx = new File(db.getDirectory(), "index"); + final File lck = new File(db.getDirectory(), "index.lock"); + assertFalse(idx.exists()); + assertFalse(lck.exists()); + + final DirCache dc = DirCache.lock(db); + assertEquals(0, lck.length()); + dc.write(); + assertEquals(12 + 20, lck.length()); + + assertTrue(dc.commit()); + assertTrue(idx.exists()); + assertFalse(lck.exists()); + assertEquals(12 + 20, idx.length()); + } + + public void testWriteEmptyReadEmpty_RealIndex() throws Exception { + final File idx = new File(db.getDirectory(), "index"); + final File lck = new File(db.getDirectory(), "index.lock"); + assertFalse(idx.exists()); + assertFalse(lck.exists()); + { + final DirCache dc = DirCache.lock(db); + dc.write(); + assertTrue(dc.commit()); + assertTrue(idx.exists()); + } + { + final DirCache dc = DirCache.read(db); + assertEquals(0, dc.getEntryCount()); + } + } + + public void testWriteEmptyLockEmpty_RealIndex() throws Exception { + final File idx = new File(db.getDirectory(), "index"); + final File lck = new File(db.getDirectory(), "index.lock"); + assertFalse(idx.exists()); + assertFalse(lck.exists()); + { + final DirCache dc = DirCache.lock(db); + dc.write(); + assertTrue(dc.commit()); + assertTrue(idx.exists()); + } + { + final DirCache dc = DirCache.lock(db); + assertEquals(0, dc.getEntryCount()); + assertTrue(idx.exists()); + assertTrue(lck.exists()); + dc.unlock(); + } + } + + public void testBuildThenClear() throws Exception { + final DirCache dc = DirCache.read(db); + + final String[] paths = { "a.", "a.b", "a/b", "a0b" }; + final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; + for (int i = 0; i < paths.length; i++) + ents[i] = new DirCacheEntry(paths[i]); + + final DirCacheBuilder b = dc.builder(); + for (int i = 0; i < ents.length; i++) + b.add(ents[i]); + b.finish(); + + assertEquals(paths.length, dc.getEntryCount()); + dc.clear(); + assertEquals(0, dc.getEntryCount()); + } + + public void testFindOnEmpty() throws Exception { + final DirCache dc = DirCache.newInCore(); + final byte[] path = Constants.encode("a"); + assertEquals(-1, dc.findEntry(path, path.length)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java new file mode 100644 index 000000000..03bb7f5e8 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderIteratorTest.java @@ -0,0 +1,97 @@ +/* + * 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.dircache; + +import java.util.Collections; + +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; + +public class DirCacheBuilderIteratorTest extends RepositoryTestCase { + public void testPathFilterGroup_DoesNotSkipTail() throws Exception { + final DirCache dc = DirCache.read(db); + + final FileMode mode = FileMode.REGULAR_FILE; + final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" }; + final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; + for (int i = 0; i < paths.length; i++) { + ents[i] = new DirCacheEntry(paths[i]); + ents[i].setFileMode(mode); + } + { + final DirCacheBuilder b = dc.builder(); + for (int i = 0; i < ents.length; i++) + b.add(ents[i]); + b.finish(); + } + + final int expIdx = 2; + final DirCacheBuilder b = dc.builder(); + final TreeWalk tw = new TreeWalk(db); + tw.reset(); + tw.addTree(new DirCacheBuildIterator(b)); + tw.setRecursive(true); + tw.setFilter(PathFilterGroup.createFromStrings(Collections + .singleton(paths[expIdx]))); + + assertTrue("found " + paths[expIdx], tw.next()); + final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class); + assertNotNull(c); + assertEquals(expIdx, c.ptr); + assertSame(ents[expIdx], c.getDirCacheEntry()); + assertEquals(paths[expIdx], tw.getPathString()); + assertEquals(mode.getBits(), tw.getRawMode(0)); + assertSame(mode, tw.getFileMode(0)); + b.add(c.getDirCacheEntry()); + + assertFalse("no more entries", tw.next()); + + b.finish(); + assertEquals(ents.length, dc.getEntryCount()); + for (int i = 0; i < ents.length; i++) + assertSame(ents[i], dc.getEntry(i)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java new file mode 100644 index 000000000..fe02a1b23 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheBuilderTest.java @@ -0,0 +1,259 @@ +/* + * 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.dircache; + +import java.io.File; + +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RepositoryTestCase; + +public class DirCacheBuilderTest extends RepositoryTestCase { + public void testBuildEmpty() throws Exception { + { + final DirCache dc = DirCache.lock(db); + final DirCacheBuilder b = dc.builder(); + assertNotNull(b); + b.finish(); + dc.write(); + assertTrue(dc.commit()); + } + { + final DirCache dc = DirCache.read(db); + assertEquals(0, dc.getEntryCount()); + } + } + + public void testBuildOneFile_FinishWriteCommit() throws Exception { + final String path = "a-file-path"; + final FileMode mode = FileMode.REGULAR_FILE; + final long lastModified = 1218123387057L; + final int length = 1342; + final DirCacheEntry entOrig; + { + final DirCache dc = DirCache.lock(db); + final DirCacheBuilder b = dc.builder(); + assertNotNull(b); + + entOrig = new DirCacheEntry(path); + entOrig.setFileMode(mode); + entOrig.setLastModified(lastModified); + entOrig.setLength(length); + + assertNotSame(path, entOrig.getPathString()); + assertEquals(path, entOrig.getPathString()); + assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); + assertEquals(mode.getBits(), entOrig.getRawMode()); + assertEquals(0, entOrig.getStage()); + assertEquals(lastModified, entOrig.getLastModified()); + assertEquals(length, entOrig.getLength()); + assertFalse(entOrig.isAssumeValid()); + b.add(entOrig); + + b.finish(); + assertEquals(1, dc.getEntryCount()); + assertSame(entOrig, dc.getEntry(0)); + + dc.write(); + assertTrue(dc.commit()); + } + { + final DirCache dc = DirCache.read(db); + assertEquals(1, dc.getEntryCount()); + + final DirCacheEntry entRead = dc.getEntry(0); + assertNotSame(entOrig, entRead); + assertEquals(path, entRead.getPathString()); + assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); + assertEquals(mode.getBits(), entOrig.getRawMode()); + assertEquals(0, entOrig.getStage()); + assertEquals(lastModified, entOrig.getLastModified()); + assertEquals(length, entOrig.getLength()); + assertFalse(entOrig.isAssumeValid()); + } + } + + public void testBuildOneFile_Commit() throws Exception { + final String path = "a-file-path"; + final FileMode mode = FileMode.REGULAR_FILE; + final long lastModified = 1218123387057L; + final int length = 1342; + final DirCacheEntry entOrig; + { + final DirCache dc = DirCache.lock(db); + final DirCacheBuilder b = dc.builder(); + assertNotNull(b); + + entOrig = new DirCacheEntry(path); + entOrig.setFileMode(mode); + entOrig.setLastModified(lastModified); + entOrig.setLength(length); + + assertNotSame(path, entOrig.getPathString()); + assertEquals(path, entOrig.getPathString()); + assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); + assertEquals(mode.getBits(), entOrig.getRawMode()); + assertEquals(0, entOrig.getStage()); + assertEquals(lastModified, entOrig.getLastModified()); + assertEquals(length, entOrig.getLength()); + assertFalse(entOrig.isAssumeValid()); + b.add(entOrig); + + assertTrue(b.commit()); + assertEquals(1, dc.getEntryCount()); + assertSame(entOrig, dc.getEntry(0)); + assertFalse(new File(db.getDirectory(), "index.lock").exists()); + } + { + final DirCache dc = DirCache.read(db); + assertEquals(1, dc.getEntryCount()); + + final DirCacheEntry entRead = dc.getEntry(0); + assertNotSame(entOrig, entRead); + assertEquals(path, entRead.getPathString()); + assertEquals(ObjectId.zeroId(), entOrig.getObjectId()); + assertEquals(mode.getBits(), entOrig.getRawMode()); + assertEquals(0, entOrig.getStage()); + assertEquals(lastModified, entOrig.getLastModified()); + assertEquals(length, entOrig.getLength()); + assertFalse(entOrig.isAssumeValid()); + } + } + + public void testFindSingleFile() throws Exception { + final String path = "a-file-path"; + final DirCache dc = DirCache.read(db); + final DirCacheBuilder b = dc.builder(); + assertNotNull(b); + + final DirCacheEntry entOrig = new DirCacheEntry(path); + assertNotSame(path, entOrig.getPathString()); + assertEquals(path, entOrig.getPathString()); + b.add(entOrig); + b.finish(); + + assertEquals(1, dc.getEntryCount()); + assertSame(entOrig, dc.getEntry(0)); + assertEquals(0, dc.findEntry(path)); + + assertEquals(-1, dc.findEntry("@@-before")); + assertEquals(0, real(dc.findEntry("@@-before"))); + + assertEquals(-2, dc.findEntry("a-zoo")); + assertEquals(1, real(dc.findEntry("a-zoo"))); + + assertSame(entOrig, dc.getEntry(path)); + } + + public void testAdd_InGitSortOrder() throws Exception { + final DirCache dc = DirCache.read(db); + + final String[] paths = { "a.", "a.b", "a/b", "a0b" }; + final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; + for (int i = 0; i < paths.length; i++) + ents[i] = new DirCacheEntry(paths[i]); + + final DirCacheBuilder b = dc.builder(); + for (int i = 0; i < ents.length; i++) + b.add(ents[i]); + b.finish(); + + assertEquals(paths.length, dc.getEntryCount()); + for (int i = 0; i < paths.length; i++) { + assertSame(ents[i], dc.getEntry(i)); + assertEquals(paths[i], dc.getEntry(i).getPathString()); + assertEquals(i, dc.findEntry(paths[i])); + assertSame(ents[i], dc.getEntry(paths[i])); + } + } + + public void testAdd_ReverseGitSortOrder() throws Exception { + final DirCache dc = DirCache.read(db); + + final String[] paths = { "a.", "a.b", "a/b", "a0b" }; + final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; + for (int i = 0; i < paths.length; i++) + ents[i] = new DirCacheEntry(paths[i]); + + final DirCacheBuilder b = dc.builder(); + for (int i = ents.length - 1; i >= 0; i--) + b.add(ents[i]); + b.finish(); + + assertEquals(paths.length, dc.getEntryCount()); + for (int i = 0; i < paths.length; i++) { + assertSame(ents[i], dc.getEntry(i)); + assertEquals(paths[i], dc.getEntry(i).getPathString()); + assertEquals(i, dc.findEntry(paths[i])); + assertSame(ents[i], dc.getEntry(paths[i])); + } + } + + public void testBuilderClear() throws Exception { + final DirCache dc = DirCache.read(db); + + final String[] paths = { "a.", "a.b", "a/b", "a0b" }; + final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; + for (int i = 0; i < paths.length; i++) + ents[i] = new DirCacheEntry(paths[i]); + { + final DirCacheBuilder b = dc.builder(); + for (int i = 0; i < ents.length; i++) + b.add(ents[i]); + b.finish(); + } + assertEquals(paths.length, dc.getEntryCount()); + { + final DirCacheBuilder b = dc.builder(); + b.finish(); + } + assertEquals(0, dc.getEntryCount()); + } + + private static int real(int eIdx) { + if (eIdx < 0) + eIdx = -(eIdx + 1); + return eIdx; + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java new file mode 100644 index 000000000..ab0c77110 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheCGitCompatabilityTest.java @@ -0,0 +1,219 @@ +/* + * 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.dircache; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.util.JGitTestUtil; + +public class DirCacheCGitCompatabilityTest extends RepositoryTestCase { + private final File index = pathOf("gitgit.index"); + + public void testReadIndex_LsFiles() throws Exception { + final Map ls = readLsFiles(); + final DirCache dc = new DirCache(index); + assertEquals(0, dc.getEntryCount()); + dc.read(); + assertEquals(ls.size(), dc.getEntryCount()); + { + final Iterator rItr = ls.values().iterator(); + for (int i = 0; rItr.hasNext(); i++) + assertEqual(rItr.next(), dc.getEntry(i)); + } + } + + public void testTreeWalk_LsFiles() throws Exception { + final Map ls = readLsFiles(); + final DirCache dc = new DirCache(index); + assertEquals(0, dc.getEntryCount()); + dc.read(); + assertEquals(ls.size(), dc.getEntryCount()); + { + final Iterator rItr = ls.values().iterator(); + final TreeWalk tw = new TreeWalk(db); + tw.reset(); + tw.setRecursive(true); + tw.addTree(new DirCacheIterator(dc)); + while (rItr.hasNext()) { + final DirCacheIterator dcItr; + + assertTrue(tw.next()); + dcItr = tw.getTree(0, DirCacheIterator.class); + assertNotNull(dcItr); + + assertEqual(rItr.next(), dcItr.getDirCacheEntry()); + } + } + } + + private static void assertEqual(final CGitIndexRecord c, + final DirCacheEntry j) { + assertNotNull(c); + assertNotNull(j); + + assertEquals(c.path, j.getPathString()); + assertEquals(c.id, j.getObjectId()); + assertEquals(c.mode, j.getRawMode()); + assertEquals(c.stage, j.getStage()); + } + + public void testReadIndex_DirCacheTree() throws Exception { + final Map cList = readLsFiles(); + final Map cTree = readLsTree(); + final DirCache dc = new DirCache(index); + assertEquals(0, dc.getEntryCount()); + dc.read(); + assertEquals(cList.size(), dc.getEntryCount()); + + final DirCacheTree jTree = dc.getCacheTree(false); + assertNotNull(jTree); + assertEquals("", jTree.getNameString()); + assertEquals("", jTree.getPathString()); + assertTrue(jTree.isValid()); + assertEquals(ObjectId + .fromString("698dd0b8d0c299f080559a1cffc7fe029479a408"), jTree + .getObjectId()); + assertEquals(cList.size(), jTree.getEntrySpan()); + + final ArrayList subtrees = new ArrayList(); + for (final CGitLsTreeRecord r : cTree.values()) { + if (FileMode.TREE.equals(r.mode)) + subtrees.add(r); + } + assertEquals(subtrees.size(), jTree.getChildCount()); + + for (int i = 0; i < jTree.getChildCount(); i++) { + final DirCacheTree sj = jTree.getChild(i); + final CGitLsTreeRecord sc = subtrees.get(i); + assertEquals(sc.path, sj.getNameString()); + assertEquals(sc.path + "/", sj.getPathString()); + assertTrue(sj.isValid()); + assertEquals(sc.id, sj.getObjectId()); + } + } + + private File pathOf(final String name) { + return JGitTestUtil.getTestResourceFile(name); + } + + private Map readLsFiles() throws Exception { + final LinkedHashMap r = new LinkedHashMap(); + final BufferedReader br = new BufferedReader(new InputStreamReader( + new FileInputStream(pathOf("gitgit.lsfiles")), "UTF-8")); + try { + String line; + while ((line = br.readLine()) != null) { + final CGitIndexRecord cr = new CGitIndexRecord(line); + r.put(cr.path, cr); + } + } finally { + br.close(); + } + return r; + } + + private Map readLsTree() throws Exception { + final LinkedHashMap r = new LinkedHashMap(); + final BufferedReader br = new BufferedReader(new InputStreamReader( + new FileInputStream(pathOf("gitgit.lstree")), "UTF-8")); + try { + String line; + while ((line = br.readLine()) != null) { + final CGitLsTreeRecord cr = new CGitLsTreeRecord(line); + r.put(cr.path, cr); + } + } finally { + br.close(); + } + return r; + } + + private static class CGitIndexRecord { + final int mode; + + final ObjectId id; + + final int stage; + + final String path; + + CGitIndexRecord(final String line) { + final int tab = line.indexOf('\t'); + final int sp1 = line.indexOf(' '); + final int sp2 = line.indexOf(' ', sp1 + 1); + mode = Integer.parseInt(line.substring(0, sp1), 8); + id = ObjectId.fromString(line.substring(sp1 + 1, sp2)); + stage = Integer.parseInt(line.substring(sp2 + 1, tab)); + path = line.substring(tab + 1); + } + } + + private static class CGitLsTreeRecord { + final int mode; + + final ObjectId id; + + final String path; + + CGitLsTreeRecord(final String line) { + final int tab = line.indexOf('\t'); + final int sp1 = line.indexOf(' '); + final int sp2 = line.indexOf(' ', sp1 + 1); + mode = Integer.parseInt(line.substring(0, sp1), 8); + id = ObjectId.fromString(line.substring(sp2 + 1, tab)); + path = line.substring(tab + 1); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheFindTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheFindTest.java new file mode 100644 index 000000000..cdc659a21 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheFindTest.java @@ -0,0 +1,92 @@ +/* + * 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.dircache; + +import org.eclipse.jgit.lib.RepositoryTestCase; + +public class DirCacheFindTest extends RepositoryTestCase { + public void testEntriesWithin() throws Exception { + final DirCache dc = DirCache.read(db); + + final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" }; + final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; + for (int i = 0; i < paths.length; i++) + ents[i] = new DirCacheEntry(paths[i]); + final int aFirst = 1; + final int aLast = 3; + + final DirCacheBuilder b = dc.builder(); + for (int i = 0; i < ents.length; i++) + b.add(ents[i]); + b.finish(); + + assertEquals(paths.length, dc.getEntryCount()); + for (int i = 0; i < ents.length; i++) + assertSame(ents[i], dc.getEntry(i)); + + { + final DirCacheEntry[] aContents = dc.getEntriesWithin("a"); + assertNotNull(aContents); + assertEquals(aLast - aFirst + 1, aContents.length); + for (int i = aFirst, j = 0; i <= aLast; i++, j++) + assertSame(ents[i], aContents[j]); + } + { + final DirCacheEntry[] aContents = dc.getEntriesWithin("a/"); + assertNotNull(aContents); + assertEquals(aLast - aFirst + 1, aContents.length); + for (int i = aFirst, j = 0; i <= aLast; i++, j++) + assertSame(ents[i], aContents[j]); + } + + assertNotNull(dc.getEntriesWithin("a.")); + assertEquals(0, dc.getEntriesWithin("a.").length); + + assertNotNull(dc.getEntriesWithin("a0b")); + assertEquals(0, dc.getEntriesWithin("a0b.").length); + + assertNotNull(dc.getEntriesWithin("zoo")); + assertEquals(0, dc.getEntriesWithin("zoo.").length); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java new file mode 100644 index 000000000..db9f684fe --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheIteratorTest.java @@ -0,0 +1,275 @@ +/* + * 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.dircache; + +import java.util.Collections; + +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; + +public class DirCacheIteratorTest extends RepositoryTestCase { + public void testEmptyTree_NoTreeWalk() throws Exception { + final DirCache dc = DirCache.read(db); + assertEquals(0, dc.getEntryCount()); + + final DirCacheIterator i = new DirCacheIterator(dc); + assertTrue(i.eof()); + } + + public void testEmptyTree_WithTreeWalk() throws Exception { + final DirCache dc = DirCache.read(db); + assertEquals(0, dc.getEntryCount()); + + final TreeWalk tw = new TreeWalk(db); + tw.reset(); + tw.addTree(new DirCacheIterator(dc)); + assertFalse(tw.next()); + } + + public void testNoSubtree_NoTreeWalk() throws Exception { + final DirCache dc = DirCache.read(db); + + final String[] paths = { "a.", "a0b" }; + final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; + for (int i = 0; i < paths.length; i++) + ents[i] = new DirCacheEntry(paths[i]); + + final DirCacheBuilder b = dc.builder(); + for (int i = 0; i < ents.length; i++) + b.add(ents[i]); + b.finish(); + + final DirCacheIterator i = new DirCacheIterator(dc); + int pathIdx = 0; + for (; !i.eof(); i.next(1)) { + assertEquals(pathIdx, i.ptr); + assertSame(ents[pathIdx], i.getDirCacheEntry()); + pathIdx++; + } + assertEquals(paths.length, pathIdx); + } + + public void testNoSubtree_WithTreeWalk() throws Exception { + final DirCache dc = DirCache.read(db); + + final String[] paths = { "a.", "a0b" }; + final FileMode[] modes = { FileMode.EXECUTABLE_FILE, FileMode.GITLINK }; + final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; + for (int i = 0; i < paths.length; i++) { + ents[i] = new DirCacheEntry(paths[i]); + ents[i].setFileMode(modes[i]); + } + + final DirCacheBuilder b = dc.builder(); + for (int i = 0; i < ents.length; i++) + b.add(ents[i]); + b.finish(); + + final DirCacheIterator i = new DirCacheIterator(dc); + final TreeWalk tw = new TreeWalk(db); + tw.reset(); + tw.addTree(i); + int pathIdx = 0; + while (tw.next()) { + assertSame(i, tw.getTree(0, DirCacheIterator.class)); + assertEquals(pathIdx, i.ptr); + assertSame(ents[pathIdx], i.getDirCacheEntry()); + assertEquals(paths[pathIdx], tw.getPathString()); + assertEquals(modes[pathIdx].getBits(), tw.getRawMode(0)); + assertSame(modes[pathIdx], tw.getFileMode(0)); + pathIdx++; + } + assertEquals(paths.length, pathIdx); + } + + public void testSingleSubtree_NoRecursion() throws Exception { + final DirCache dc = DirCache.read(db); + + final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" }; + final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; + for (int i = 0; i < paths.length; i++) { + ents[i] = new DirCacheEntry(paths[i]); + ents[i].setFileMode(FileMode.REGULAR_FILE); + } + + final DirCacheBuilder b = dc.builder(); + for (int i = 0; i < ents.length; i++) + b.add(ents[i]); + b.finish(); + + final String[] expPaths = { "a.", "a", "a0b" }; + final FileMode[] expModes = { FileMode.REGULAR_FILE, FileMode.TREE, + FileMode.REGULAR_FILE }; + final int expPos[] = { 0, -1, 4 }; + + final DirCacheIterator i = new DirCacheIterator(dc); + final TreeWalk tw = new TreeWalk(db); + tw.reset(); + tw.addTree(i); + tw.setRecursive(false); + int pathIdx = 0; + while (tw.next()) { + assertSame(i, tw.getTree(0, DirCacheIterator.class)); + assertEquals(expModes[pathIdx].getBits(), tw.getRawMode(0)); + assertSame(expModes[pathIdx], tw.getFileMode(0)); + assertEquals(expPaths[pathIdx], tw.getPathString()); + + if (expPos[pathIdx] >= 0) { + assertEquals(expPos[pathIdx], i.ptr); + assertSame(ents[expPos[pathIdx]], i.getDirCacheEntry()); + } else { + assertSame(FileMode.TREE, tw.getFileMode(0)); + } + + pathIdx++; + } + assertEquals(expPaths.length, pathIdx); + } + + public void testSingleSubtree_Recursive() throws Exception { + final DirCache dc = DirCache.read(db); + + final FileMode mode = FileMode.REGULAR_FILE; + final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" }; + final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; + for (int i = 0; i < paths.length; i++) { + ents[i] = new DirCacheEntry(paths[i]); + ents[i].setFileMode(mode); + } + + final DirCacheBuilder b = dc.builder(); + for (int i = 0; i < ents.length; i++) + b.add(ents[i]); + b.finish(); + + final DirCacheIterator i = new DirCacheIterator(dc); + final TreeWalk tw = new TreeWalk(db); + tw.reset(); + tw.addTree(i); + tw.setRecursive(true); + int pathIdx = 0; + while (tw.next()) { + final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class); + assertNotNull(c); + assertEquals(pathIdx, c.ptr); + assertSame(ents[pathIdx], c.getDirCacheEntry()); + assertEquals(paths[pathIdx], tw.getPathString()); + assertEquals(mode.getBits(), tw.getRawMode(0)); + assertSame(mode, tw.getFileMode(0)); + pathIdx++; + } + assertEquals(paths.length, pathIdx); + } + + public void testTwoLevelSubtree_Recursive() throws Exception { + final DirCache dc = DirCache.read(db); + + final FileMode mode = FileMode.REGULAR_FILE; + final String[] paths = { "a.", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" }; + final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; + for (int i = 0; i < paths.length; i++) { + ents[i] = new DirCacheEntry(paths[i]); + ents[i].setFileMode(mode); + } + + final DirCacheBuilder b = dc.builder(); + for (int i = 0; i < ents.length; i++) + b.add(ents[i]); + b.finish(); + + final TreeWalk tw = new TreeWalk(db); + tw.reset(); + tw.addTree(new DirCacheIterator(dc)); + tw.setRecursive(true); + int pathIdx = 0; + while (tw.next()) { + final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class); + assertNotNull(c); + assertEquals(pathIdx, c.ptr); + assertSame(ents[pathIdx], c.getDirCacheEntry()); + assertEquals(paths[pathIdx], tw.getPathString()); + assertEquals(mode.getBits(), tw.getRawMode(0)); + assertSame(mode, tw.getFileMode(0)); + pathIdx++; + } + assertEquals(paths.length, pathIdx); + } + + public void testTwoLevelSubtree_FilterPath() throws Exception { + final DirCache dc = DirCache.read(db); + + final FileMode mode = FileMode.REGULAR_FILE; + final String[] paths = { "a.", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" }; + final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; + for (int i = 0; i < paths.length; i++) { + ents[i] = new DirCacheEntry(paths[i]); + ents[i].setFileMode(mode); + } + + final DirCacheBuilder b = dc.builder(); + for (int i = 0; i < ents.length; i++) + b.add(ents[i]); + b.finish(); + + final TreeWalk tw = new TreeWalk(db); + for (int victimIdx = 0; victimIdx < paths.length; victimIdx++) { + tw.reset(); + tw.addTree(new DirCacheIterator(dc)); + tw.setFilter(PathFilterGroup.createFromStrings(Collections + .singleton(paths[victimIdx]))); + tw.setRecursive(tw.getFilter().shouldBeRecursive()); + assertTrue(tw.next()); + final DirCacheIterator c = tw.getTree(0, DirCacheIterator.class); + assertNotNull(c); + assertEquals(victimIdx, c.ptr); + assertSame(ents[victimIdx], c.getDirCacheEntry()); + assertEquals(paths[victimIdx], tw.getPathString()); + assertEquals(mode.getBits(), tw.getRawMode(0)); + assertSame(mode, tw.getFileMode(0)); + assertFalse(tw.next()); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheLargePathTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheLargePathTest.java new file mode 100644 index 000000000..ceaadf97b --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheLargePathTest.java @@ -0,0 +1,112 @@ +/* + * 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.dircache; + +import java.io.IOException; + +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.RepositoryTestCase; + +public class DirCacheLargePathTest extends RepositoryTestCase { + public void testPath_4090() throws Exception { + testLongPath(4090); + } + + public void testPath_4094() throws Exception { + testLongPath(4094); + } + + public void testPath_4095() throws Exception { + testLongPath(4095); + } + + public void testPath_4096() throws Exception { + testLongPath(4096); + } + + public void testPath_16384() throws Exception { + testLongPath(16384); + } + + private void testLongPath(final int len) throws CorruptObjectException, + IOException { + final String longPath = makeLongPath(len); + final String shortPath = "~~~ shorter-path"; + + final DirCacheEntry longEnt = new DirCacheEntry(longPath); + final DirCacheEntry shortEnt = new DirCacheEntry(shortPath); + assertEquals(longPath, longEnt.getPathString()); + assertEquals(shortPath, shortEnt.getPathString()); + + { + final DirCache dc1 = DirCache.lock(db); + { + final DirCacheBuilder b = dc1.builder(); + b.add(longEnt); + b.add(shortEnt); + assertTrue(b.commit()); + } + assertEquals(2, dc1.getEntryCount()); + assertSame(longEnt, dc1.getEntry(0)); + assertSame(shortEnt, dc1.getEntry(1)); + } + { + final DirCache dc2 = DirCache.read(db); + assertEquals(2, dc2.getEntryCount()); + + assertNotSame(longEnt, dc2.getEntry(0)); + assertEquals(longPath, dc2.getEntry(0).getPathString()); + + assertNotSame(shortEnt, dc2.getEntry(1)); + assertEquals(shortPath, dc2.getEntry(1).getPathString()); + } + } + + private static String makeLongPath(final int len) { + final StringBuilder r = new StringBuilder(len); + for (int i = 0; i < len; i++) + r.append('a' + (i % 26)); + return r.toString(); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheTreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheTreeTest.java new file mode 100644 index 000000000..b84b240a7 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheTreeTest.java @@ -0,0 +1,189 @@ +/* + * 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.dircache; + +import java.io.IOException; + +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.lib.RepositoryTestCase; + +public class DirCacheTreeTest extends RepositoryTestCase { + public void testEmptyCache_NoCacheTree() throws Exception { + final DirCache dc = DirCache.read(db); + assertNull(dc.getCacheTree(false)); + } + + public void testEmptyCache_CreateEmptyCacheTree() throws Exception { + final DirCache dc = DirCache.read(db); + final DirCacheTree tree = dc.getCacheTree(true); + assertNotNull(tree); + assertSame(tree, dc.getCacheTree(false)); + assertSame(tree, dc.getCacheTree(true)); + assertEquals("", tree.getNameString()); + assertEquals("", tree.getPathString()); + assertEquals(0, tree.getChildCount()); + assertEquals(0, tree.getEntrySpan()); + assertFalse(tree.isValid()); + } + + public void testEmptyCache_Clear_NoCacheTree() throws Exception { + final DirCache dc = DirCache.read(db); + final DirCacheTree tree = dc.getCacheTree(true); + assertNotNull(tree); + dc.clear(); + assertNull(dc.getCacheTree(false)); + assertNotSame(tree, dc.getCacheTree(true)); + } + + public void testSingleSubtree() throws Exception { + final DirCache dc = DirCache.read(db); + + final String[] paths = { "a.", "a/b", "a/c", "a/d", "a0b" }; + final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; + for (int i = 0; i < paths.length; i++) + ents[i] = new DirCacheEntry(paths[i]); + final int aFirst = 1; + final int aLast = 3; + + final DirCacheBuilder b = dc.builder(); + for (int i = 0; i < ents.length; i++) + b.add(ents[i]); + b.finish(); + + assertNull(dc.getCacheTree(false)); + final DirCacheTree root = dc.getCacheTree(true); + assertNotNull(root); + assertSame(root, dc.getCacheTree(true)); + assertEquals("", root.getNameString()); + assertEquals("", root.getPathString()); + assertEquals(1, root.getChildCount()); + assertEquals(dc.getEntryCount(), root.getEntrySpan()); + assertFalse(root.isValid()); + + final DirCacheTree aTree = root.getChild(0); + assertNotNull(aTree); + assertSame(aTree, root.getChild(0)); + assertEquals("a", aTree.getNameString()); + assertEquals("a/", aTree.getPathString()); + assertEquals(0, aTree.getChildCount()); + assertEquals(aLast - aFirst + 1, aTree.getEntrySpan()); + assertFalse(aTree.isValid()); + } + + public void testTwoLevelSubtree() throws Exception { + final DirCache dc = DirCache.read(db); + + final String[] paths = { "a.", "a/b", "a/c/e", "a/c/f", "a/d", "a0b" }; + final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; + for (int i = 0; i < paths.length; i++) + ents[i] = new DirCacheEntry(paths[i]); + final int aFirst = 1; + final int aLast = 4; + final int acFirst = 2; + final int acLast = 3; + + final DirCacheBuilder b = dc.builder(); + for (int i = 0; i < ents.length; i++) + b.add(ents[i]); + b.finish(); + + assertNull(dc.getCacheTree(false)); + final DirCacheTree root = dc.getCacheTree(true); + assertNotNull(root); + assertSame(root, dc.getCacheTree(true)); + assertEquals("", root.getNameString()); + assertEquals("", root.getPathString()); + assertEquals(1, root.getChildCount()); + assertEquals(dc.getEntryCount(), root.getEntrySpan()); + assertFalse(root.isValid()); + + final DirCacheTree aTree = root.getChild(0); + assertNotNull(aTree); + assertSame(aTree, root.getChild(0)); + assertEquals("a", aTree.getNameString()); + assertEquals("a/", aTree.getPathString()); + assertEquals(1, aTree.getChildCount()); + assertEquals(aLast - aFirst + 1, aTree.getEntrySpan()); + assertFalse(aTree.isValid()); + + final DirCacheTree acTree = aTree.getChild(0); + assertNotNull(acTree); + assertSame(acTree, aTree.getChild(0)); + assertEquals("c", acTree.getNameString()); + assertEquals("a/c/", acTree.getPathString()); + assertEquals(0, acTree.getChildCount()); + assertEquals(acLast - acFirst + 1, acTree.getEntrySpan()); + assertFalse(acTree.isValid()); + } + + /** + * We had bugs related to buffer size in the DirCache. This test creates an + * index larger than the default BufferedInputStream buffer size. This made + * the DirCache unable to read the extensions when index size exceeded the + * buffer size (in some cases at least). + * + * @throws CorruptObjectException + * @throws IOException + */ + public void testWriteReadTree() throws CorruptObjectException, IOException { + final DirCache dc = DirCache.lock(db); + + final String A = String.format("a%2000s", "a"); + final String B = String.format("b%2000s", "b"); + final String[] paths = { A + ".", A + "." + B, A + "/" + B, A + "0" + B }; + final DirCacheEntry[] ents = new DirCacheEntry[paths.length]; + for (int i = 0; i < paths.length; i++) + ents[i] = new DirCacheEntry(paths[i]); + + final DirCacheBuilder b = dc.builder(); + for (int i = 0; i < ents.length; i++) + b.add(ents[i]); + + b.commit(); + DirCache read = DirCache.read(db); + + assertEquals(paths.length, read.getEntryCount()); + assertEquals(1, read.getCacheTree(true).getChildCount()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/fnmatch/FileNameMatcherTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/fnmatch/FileNameMatcherTest.java new file mode 100644 index 000000000..752a1e2f6 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/fnmatch/FileNameMatcherTest.java @@ -0,0 +1,764 @@ +/* + * Copyright (C) 2008, Florian Koeberle + * Copyright (C) 2008, Florian Köberle + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 org.eclipse.jgit.errors.InvalidPatternException; +import org.eclipse.jgit.fnmatch.FileNameMatcher; + +import junit.framework.TestCase; + +public class FileNameMatcherTest extends TestCase { + + private void assertMatch(final String pattern, final String input, + final boolean matchExpected, final boolean appendCanMatchExpected) + throws InvalidPatternException { + final FileNameMatcher matcher = new FileNameMatcher(pattern, null); + matcher.append(input); + assertEquals(matchExpected, matcher.isMatch()); + assertEquals(appendCanMatchExpected, matcher.canAppendMatch()); + } + + private void assertFileNameMatch(final String pattern, final String input, + final char excludedCharacter, final boolean matchExpected, + final boolean appendCanMatchExpected) + throws InvalidPatternException { + final FileNameMatcher matcher = new FileNameMatcher(pattern, + new Character(excludedCharacter)); + matcher.append(input); + assertEquals(matchExpected, matcher.isMatch()); + assertEquals(appendCanMatchExpected, matcher.canAppendMatch()); + } + + public void testVerySimplePatternCase0() throws Exception { + assertMatch("", "", true, false); + } + + public void testVerySimplePatternCase1() throws Exception { + assertMatch("ab", "a", false, true); + } + + public void testVerySimplePatternCase2() throws Exception { + assertMatch("ab", "ab", true, false); + } + + public void testVerySimplePatternCase3() throws Exception { + assertMatch("ab", "ac", false, false); + } + + public void testVerySimplePatternCase4() throws Exception { + assertMatch("ab", "abc", false, false); + } + + public void testVerySimpleWirdcardCase0() throws Exception { + assertMatch("?", "a", true, false); + } + + public void testVerySimpleWildCardCase1() throws Exception { + assertMatch("??", "a", false, true); + } + + public void testVerySimpleWildCardCase2() throws Exception { + assertMatch("??", "ab", true, false); + } + + public void testVerySimpleWildCardCase3() throws Exception { + assertMatch("??", "abc", false, false); + } + + public void testVerySimpleStarCase0() throws Exception { + assertMatch("*", "", true, true); + } + + public void testVerySimpleStarCase1() throws Exception { + assertMatch("*", "a", true, true); + } + + public void testVerySimpleStarCase2() throws Exception { + assertMatch("*", "ab", true, true); + } + + public void testSimpleStarCase0() throws Exception { + assertMatch("a*b", "a", false, true); + } + + public void testSimpleStarCase1() throws Exception { + assertMatch("a*c", "ac", true, true); + } + + public void testSimpleStarCase2() throws Exception { + assertMatch("a*c", "ab", false, true); + } + + public void testSimpleStarCase3() throws Exception { + assertMatch("a*c", "abc", true, true); + } + + public void testManySolutionsCase0() throws Exception { + assertMatch("a*a*a", "aaa", true, true); + } + + public void testManySolutionsCase1() throws Exception { + assertMatch("a*a*a", "aaaa", true, true); + } + + public void testManySolutionsCase2() throws Exception { + assertMatch("a*a*a", "ababa", true, true); + } + + public void testManySolutionsCase3() throws Exception { + assertMatch("a*a*a", "aaaaaaaa", true, true); + } + + public void testManySolutionsCase4() throws Exception { + assertMatch("a*a*a", "aaaaaaab", false, true); + } + + public void testVerySimpleGroupCase0() throws Exception { + assertMatch("[ab]", "a", true, false); + } + + public void testVerySimpleGroupCase1() throws Exception { + assertMatch("[ab]", "b", true, false); + } + + public void testVerySimpleGroupCase2() throws Exception { + assertMatch("[ab]", "ab", false, false); + } + + public void testVerySimpleGroupRangeCase0() throws Exception { + assertMatch("[b-d]", "a", false, false); + } + + public void testVerySimpleGroupRangeCase1() throws Exception { + assertMatch("[b-d]", "b", true, false); + } + + public void testVerySimpleGroupRangeCase2() throws Exception { + assertMatch("[b-d]", "c", true, false); + } + + public void testVerySimpleGroupRangeCase3() throws Exception { + assertMatch("[b-d]", "d", true, false); + } + + public void testVerySimpleGroupRangeCase4() throws Exception { + assertMatch("[b-d]", "e", false, false); + } + + public void testVerySimpleGroupRangeCase5() throws Exception { + assertMatch("[b-d]", "-", false, false); + } + + public void testTwoGroupsCase0() throws Exception { + assertMatch("[b-d][ab]", "bb", true, false); + } + + public void testTwoGroupsCase1() throws Exception { + assertMatch("[b-d][ab]", "ca", true, false); + } + + public void testTwoGroupsCase2() throws Exception { + assertMatch("[b-d][ab]", "fa", false, false); + } + + public void testTwoGroupsCase3() throws Exception { + assertMatch("[b-d][ab]", "bc", false, false); + } + + public void testTwoRangesInOneGroupCase0() throws Exception { + assertMatch("[b-ce-e]", "a", false, false); + } + + public void testTwoRangesInOneGroupCase1() throws Exception { + assertMatch("[b-ce-e]", "b", true, false); + } + + public void testTwoRangesInOneGroupCase2() throws Exception { + assertMatch("[b-ce-e]", "c", true, false); + } + + public void testTwoRangesInOneGroupCase3() throws Exception { + assertMatch("[b-ce-e]", "d", false, false); + } + + public void testTwoRangesInOneGroupCase4() throws Exception { + assertMatch("[b-ce-e]", "e", true, false); + } + + public void testTwoRangesInOneGroupCase5() throws Exception { + assertMatch("[b-ce-e]", "f", false, false); + } + + public void testIncompleteRangesInOneGroupCase0() throws Exception { + assertMatch("a[b-]", "ab", true, false); + } + + public void testIncompleteRangesInOneGroupCase1() throws Exception { + assertMatch("a[b-]", "ac", false, false); + } + + public void testIncompleteRangesInOneGroupCase2() throws Exception { + assertMatch("a[b-]", "a-", true, false); + } + + public void testCombinedRangesInOneGroupCase0() throws Exception { + assertMatch("[a-c-e]", "b", true, false); + } + + /** + * The c belongs to the range a-c. "-e" is no valid range so d should not + * match. + * + * @throws Exception + * for some reasons + */ + public void testCombinedRangesInOneGroupCase1() throws Exception { + assertMatch("[a-c-e]", "d", false, false); + } + + public void testCombinedRangesInOneGroupCase2() throws Exception { + assertMatch("[a-c-e]", "e", true, false); + } + + public void testInversedGroupCase0() throws Exception { + assertMatch("[!b-c]", "a", true, false); + } + + public void testInversedGroupCase1() throws Exception { + assertMatch("[!b-c]", "b", false, false); + } + + public void testInversedGroupCase2() throws Exception { + assertMatch("[!b-c]", "c", false, false); + } + + public void testInversedGroupCase3() throws Exception { + assertMatch("[!b-c]", "d", true, false); + } + + public void testAlphaGroupCase0() throws Exception { + assertMatch("[[:alpha:]]", "d", true, false); + } + + public void testAlphaGroupCase1() throws Exception { + assertMatch("[[:alpha:]]", ":", false, false); + } + + public void testAlphaGroupCase2() throws Exception { + // \u00f6 = 'o' with dots on it + assertMatch("[[:alpha:]]", "\u00f6", true, false); + } + + public void test2AlphaGroupsCase0() throws Exception { + // \u00f6 = 'o' with dots on it + assertMatch("[[:alpha:]][[:alpha:]]", "a\u00f6", true, false); + assertMatch("[[:alpha:]][[:alpha:]]", "a1", false, false); + } + + public void testAlnumGroupCase0() throws Exception { + assertMatch("[[:alnum:]]", "a", true, false); + } + + public void testAlnumGroupCase1() throws Exception { + assertMatch("[[:alnum:]]", "1", true, false); + } + + public void testAlnumGroupCase2() throws Exception { + assertMatch("[[:alnum:]]", ":", false, false); + } + + public void testBlankGroupCase0() throws Exception { + assertMatch("[[:blank:]]", " ", true, false); + } + + public void testBlankGroupCase1() throws Exception { + assertMatch("[[:blank:]]", "\t", true, false); + } + + public void testBlankGroupCase2() throws Exception { + assertMatch("[[:blank:]]", "\r", false, false); + } + + public void testBlankGroupCase3() throws Exception { + assertMatch("[[:blank:]]", "\n", false, false); + } + + public void testBlankGroupCase4() throws Exception { + assertMatch("[[:blank:]]", "a", false, false); + } + + public void testCntrlGroupCase0() throws Exception { + assertMatch("[[:cntrl:]]", "a", false, false); + } + + public void testCntrlGroupCase1() throws Exception { + assertMatch("[[:cntrl:]]", String.valueOf((char) 7), true, false); + } + + public void testDigitGroupCase0() throws Exception { + assertMatch("[[:digit:]]", "0", true, false); + } + + public void testDigitGroupCase1() throws Exception { + assertMatch("[[:digit:]]", "5", true, false); + } + + public void testDigitGroupCase2() throws Exception { + assertMatch("[[:digit:]]", "9", true, false); + } + + public void testDigitGroupCase3() throws Exception { + // \u06f9 = EXTENDED ARABIC-INDIC DIGIT NINE + assertMatch("[[:digit:]]", "\u06f9", true, false); + } + + public void testDigitGroupCase4() throws Exception { + assertMatch("[[:digit:]]", "a", false, false); + } + + public void testDigitGroupCase5() throws Exception { + assertMatch("[[:digit:]]", "]", false, false); + } + + public void testGraphGroupCase0() throws Exception { + assertMatch("[[:graph:]]", "]", true, false); + } + + public void testGraphGroupCase1() throws Exception { + assertMatch("[[:graph:]]", "a", true, false); + } + + public void testGraphGroupCase2() throws Exception { + assertMatch("[[:graph:]]", ".", true, false); + } + + public void testGraphGroupCase3() throws Exception { + assertMatch("[[:graph:]]", "0", true, false); + } + + public void testGraphGroupCase4() throws Exception { + assertMatch("[[:graph:]]", " ", false, false); + } + + public void testGraphGroupCase5() throws Exception { + // \u00f6 = 'o' with dots on it + assertMatch("[[:graph:]]", "\u00f6", true, false); + } + + public void testLowerGroupCase0() throws Exception { + assertMatch("[[:lower:]]", "a", true, false); + } + + public void testLowerGroupCase1() throws Exception { + assertMatch("[[:lower:]]", "h", true, false); + } + + public void testLowerGroupCase2() throws Exception { + assertMatch("[[:lower:]]", "A", false, false); + } + + public void testLowerGroupCase3() throws Exception { + assertMatch("[[:lower:]]", "H", false, false); + } + + public void testLowerGroupCase4() throws Exception { + // \u00e4 = small 'a' with dots on it + assertMatch("[[:lower:]]", "\u00e4", true, false); + } + + public void testLowerGroupCase5() throws Exception { + assertMatch("[[:lower:]]", ".", false, false); + } + + public void testPrintGroupCase0() throws Exception { + assertMatch("[[:print:]]", "]", true, false); + } + + public void testPrintGroupCase1() throws Exception { + assertMatch("[[:print:]]", "a", true, false); + } + + public void testPrintGroupCase2() throws Exception { + assertMatch("[[:print:]]", ".", true, false); + } + + public void testPrintGroupCase3() throws Exception { + assertMatch("[[:print:]]", "0", true, false); + } + + public void testPrintGroupCase4() throws Exception { + assertMatch("[[:print:]]", " ", true, false); + } + + public void testPrintGroupCase5() throws Exception { + // \u00f6 = 'o' with dots on it + assertMatch("[[:print:]]", "\u00f6", true, false); + } + + public void testPunctGroupCase0() throws Exception { + assertMatch("[[:punct:]]", ".", true, false); + } + + public void testPunctGroupCase1() throws Exception { + assertMatch("[[:punct:]]", "@", true, false); + } + + public void testPunctGroupCase2() throws Exception { + assertMatch("[[:punct:]]", " ", false, false); + } + + public void testPunctGroupCase3() throws Exception { + assertMatch("[[:punct:]]", "a", false, false); + } + + public void testSpaceGroupCase0() throws Exception { + assertMatch("[[:space:]]", " ", true, false); + } + + public void testSpaceGroupCase1() throws Exception { + assertMatch("[[:space:]]", "\t", true, false); + } + + public void testSpaceGroupCase2() throws Exception { + assertMatch("[[:space:]]", "\r", true, false); + } + + public void testSpaceGroupCase3() throws Exception { + assertMatch("[[:space:]]", "\n", true, false); + } + + public void testSpaceGroupCase4() throws Exception { + assertMatch("[[:space:]]", "a", false, false); + } + + public void testUpperGroupCase0() throws Exception { + assertMatch("[[:upper:]]", "a", false, false); + } + + public void testUpperGroupCase1() throws Exception { + assertMatch("[[:upper:]]", "h", false, false); + } + + public void testUpperGroupCase2() throws Exception { + assertMatch("[[:upper:]]", "A", true, false); + } + + public void testUpperGroupCase3() throws Exception { + assertMatch("[[:upper:]]", "H", true, false); + } + + public void testUpperGroupCase4() throws Exception { + // \u00c4 = 'A' with dots on it + assertMatch("[[:upper:]]", "\u00c4", true, false); + } + + public void testUpperGroupCase5() throws Exception { + assertMatch("[[:upper:]]", ".", false, false); + } + + public void testXDigitGroupCase0() throws Exception { + assertMatch("[[:xdigit:]]", "a", true, false); + } + + public void testXDigitGroupCase1() throws Exception { + assertMatch("[[:xdigit:]]", "d", true, false); + } + + public void testXDigitGroupCase2() throws Exception { + assertMatch("[[:xdigit:]]", "f", true, false); + } + + public void testXDigitGroupCase3() throws Exception { + assertMatch("[[:xdigit:]]", "0", true, false); + } + + public void testXDigitGroupCase4() throws Exception { + assertMatch("[[:xdigit:]]", "5", true, false); + } + + public void testXDigitGroupCase5() throws Exception { + assertMatch("[[:xdigit:]]", "9", true, false); + } + + public void testXDigitGroupCase6() throws Exception { + assertMatch("[[:xdigit:]]", "Û¹", false, false); + } + + public void testXDigitGroupCase7() throws Exception { + assertMatch("[[:xdigit:]]", ".", false, false); + } + + public void testWordroupCase0() throws Exception { + assertMatch("[[:word:]]", "g", true, false); + } + + public void testWordroupCase1() throws Exception { + // \u00f6 = 'o' with dots on it + assertMatch("[[:word:]]", "\u00f6", true, false); + } + + public void testWordroupCase2() throws Exception { + assertMatch("[[:word:]]", "5", true, false); + } + + public void testWordroupCase3() throws Exception { + assertMatch("[[:word:]]", "_", true, false); + } + + public void testWordroupCase4() throws Exception { + assertMatch("[[:word:]]", " ", false, false); + } + + public void testWordroupCase5() throws Exception { + assertMatch("[[:word:]]", ".", false, false); + } + + public void testMixedGroupCase0() throws Exception { + assertMatch("[A[:lower:]C3-5]", "A", true, false); + } + + public void testMixedGroupCase1() throws Exception { + assertMatch("[A[:lower:]C3-5]", "C", true, false); + } + + public void testMixedGroupCase2() throws Exception { + assertMatch("[A[:lower:]C3-5]", "e", true, false); + } + + public void testMixedGroupCase3() throws Exception { + assertMatch("[A[:lower:]C3-5]", "3", true, false); + } + + public void testMixedGroupCase4() throws Exception { + assertMatch("[A[:lower:]C3-5]", "4", true, false); + } + + public void testMixedGroupCase5() throws Exception { + assertMatch("[A[:lower:]C3-5]", "5", true, false); + } + + public void testMixedGroupCase6() throws Exception { + assertMatch("[A[:lower:]C3-5]", "B", false, false); + } + + public void testMixedGroupCase7() throws Exception { + assertMatch("[A[:lower:]C3-5]", "2", false, false); + } + + public void testMixedGroupCase8() throws Exception { + assertMatch("[A[:lower:]C3-5]", "6", false, false); + } + + public void testMixedGroupCase9() throws Exception { + assertMatch("[A[:lower:]C3-5]", ".", false, false); + } + + public void testSpecialGroupCase0() throws Exception { + assertMatch("[[]", "[", true, false); + } + + public void testSpecialGroupCase1() throws Exception { + assertMatch("[]]", "]", true, false); + } + + public void testSpecialGroupCase2() throws Exception { + assertMatch("[]a]", "]", true, false); + } + + public void testSpecialGroupCase3() throws Exception { + assertMatch("[a[]", "[", true, false); + } + + public void testSpecialGroupCase4() throws Exception { + assertMatch("[a[]", "a", true, false); + } + + public void testSpecialGroupCase5() throws Exception { + assertMatch("[!]]", "]", false, false); + } + + public void testSpecialGroupCase6() throws Exception { + assertMatch("[!]]", "x", true, false); + } + + public void testSpecialGroupCase7() throws Exception { + assertMatch("[:]]", ":]", true, false); + } + + public void testSpecialGroupCase8() throws Exception { + assertMatch("[:]]", ":", false, true); + } + + public void testSpecialGroupCase9() throws Exception { + try { + assertMatch("[[:]", ":", true, true); + fail("InvalidPatternException expected"); + } catch (InvalidPatternException e) { + // expected + } + } + + public void testUnsupportedGroupCase0() throws Exception { + try { + assertMatch("[[=a=]]", "b", false, false); + fail("InvalidPatternException expected"); + } catch (InvalidPatternException e) { + assertTrue(e.getMessage().contains("[=a=]")); + } + } + + public void testUnsupportedGroupCase1() throws Exception { + try { + assertMatch("[[.a.]]", "b", false, false); + fail("InvalidPatternException expected"); + } catch (InvalidPatternException e) { + assertTrue(e.getMessage().contains("[.a.]")); + } + } + + public void testFilePathSimpleCase() throws Exception { + assertFileNameMatch("a/b", "a/b", '/', true, false); + } + + public void testFilePathCase0() throws Exception { + assertFileNameMatch("a*b", "a/b", '/', false, false); + } + + public void testFilePathCase1() throws Exception { + assertFileNameMatch("a?b", "a/b", '/', false, false); + } + + public void testFilePathCase2() throws Exception { + assertFileNameMatch("a*b", "a\\b", '\\', false, false); + } + + public void testFilePathCase3() throws Exception { + assertFileNameMatch("a?b", "a\\b", '\\', false, false); + } + + public void testReset() throws Exception { + final String pattern = "helloworld"; + final FileNameMatcher matcher = new FileNameMatcher(pattern, null); + matcher.append("helloworld"); + assertEquals(true, matcher.isMatch()); + assertEquals(false, matcher.canAppendMatch()); + matcher.reset(); + matcher.append("hello"); + assertEquals(false, matcher.isMatch()); + assertEquals(true, matcher.canAppendMatch()); + matcher.append("world"); + assertEquals(true, matcher.isMatch()); + assertEquals(false, matcher.canAppendMatch()); + matcher.append("to much"); + assertEquals(false, matcher.isMatch()); + assertEquals(false, matcher.canAppendMatch()); + matcher.reset(); + matcher.append("helloworld"); + assertEquals(true, matcher.isMatch()); + assertEquals(false, matcher.canAppendMatch()); + } + + public void testCreateMatcherForSuffix() throws Exception { + final String pattern = "helloworld"; + final FileNameMatcher matcher = new FileNameMatcher(pattern, null); + matcher.append("hello"); + final FileNameMatcher childMatcher = matcher.createMatcherForSuffix(); + assertEquals(false, matcher.isMatch()); + assertEquals(true, matcher.canAppendMatch()); + assertEquals(false, childMatcher.isMatch()); + assertEquals(true, childMatcher.canAppendMatch()); + matcher.append("world"); + assertEquals(true, matcher.isMatch()); + assertEquals(false, matcher.canAppendMatch()); + assertEquals(false, childMatcher.isMatch()); + assertEquals(true, childMatcher.canAppendMatch()); + childMatcher.append("world"); + assertEquals(true, matcher.isMatch()); + assertEquals(false, matcher.canAppendMatch()); + assertEquals(true, childMatcher.isMatch()); + assertEquals(false, childMatcher.canAppendMatch()); + childMatcher.reset(); + assertEquals(true, matcher.isMatch()); + assertEquals(false, matcher.canAppendMatch()); + assertEquals(false, childMatcher.isMatch()); + assertEquals(true, childMatcher.canAppendMatch()); + childMatcher.append("world"); + assertEquals(true, matcher.isMatch()); + assertEquals(false, matcher.canAppendMatch()); + assertEquals(true, childMatcher.isMatch()); + assertEquals(false, childMatcher.canAppendMatch()); + } + + public void testCopyConstructor() throws Exception { + final String pattern = "helloworld"; + final FileNameMatcher matcher = new FileNameMatcher(pattern, null); + matcher.append("hello"); + final FileNameMatcher copy = new FileNameMatcher(matcher); + assertEquals(false, matcher.isMatch()); + assertEquals(true, matcher.canAppendMatch()); + assertEquals(false, copy.isMatch()); + assertEquals(true, copy.canAppendMatch()); + matcher.append("world"); + assertEquals(true, matcher.isMatch()); + assertEquals(false, matcher.canAppendMatch()); + assertEquals(false, copy.isMatch()); + assertEquals(true, copy.canAppendMatch()); + copy.append("world"); + assertEquals(true, matcher.isMatch()); + assertEquals(false, matcher.canAppendMatch()); + assertEquals(true, copy.isMatch()); + assertEquals(false, copy.canAppendMatch()); + copy.reset(); + assertEquals(true, matcher.isMatch()); + assertEquals(false, matcher.canAppendMatch()); + assertEquals(false, copy.isMatch()); + assertEquals(true, copy.canAppendMatch()); + copy.append("helloworld"); + assertEquals(true, matcher.isMatch()); + assertEquals(false, matcher.canAppendMatch()); + assertEquals(true, copy.isMatch()); + assertEquals(false, copy.canAppendMatch()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java new file mode 100644 index 000000000..45f8907da --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/AbbreviatedObjectIdTest.java @@ -0,0 +1,351 @@ +/* + * 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 junit.framework.TestCase; + +public class AbbreviatedObjectIdTest extends TestCase { + public void testEmpty_FromByteArray() { + final AbbreviatedObjectId i; + i = AbbreviatedObjectId.fromString(new byte[] {}, 0, 0); + assertNotNull(i); + assertEquals(0, i.length()); + assertFalse(i.isComplete()); + assertEquals("", i.name()); + } + + public void testEmpty_FromString() { + final AbbreviatedObjectId i = AbbreviatedObjectId.fromString(""); + assertNotNull(i); + assertEquals(0, i.length()); + assertFalse(i.isComplete()); + assertEquals("", i.name()); + } + + public void testFull_FromByteArray() { + final String s = "7b6e8067ec96acef9a4184b43210d583b6d2f99a"; + final byte[] b = Constants.encodeASCII(s); + final AbbreviatedObjectId i = AbbreviatedObjectId.fromString(b, 0, + b.length); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertTrue(i.isComplete()); + assertEquals(s, i.name()); + + final ObjectId f = i.toObjectId(); + assertNotNull(f); + assertEquals(ObjectId.fromString(s), f); + assertEquals(f.hashCode(), i.hashCode()); + } + + public void testFull_FromString() { + final String s = "7b6e8067ec96acef9a4184b43210d583b6d2f99a"; + final AbbreviatedObjectId i = AbbreviatedObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertTrue(i.isComplete()); + assertEquals(s, i.name()); + + final ObjectId f = i.toObjectId(); + assertNotNull(f); + assertEquals(ObjectId.fromString(s), f); + assertEquals(f.hashCode(), i.hashCode()); + } + + public void test1_FromString() { + final String s = "7"; + final AbbreviatedObjectId i = AbbreviatedObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toObjectId()); + } + + public void test2_FromString() { + final String s = "7b"; + final AbbreviatedObjectId i = AbbreviatedObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toObjectId()); + } + + public void test3_FromString() { + final String s = "7b6"; + final AbbreviatedObjectId i = AbbreviatedObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toObjectId()); + } + + public void test4_FromString() { + final String s = "7b6e"; + final AbbreviatedObjectId i = AbbreviatedObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toObjectId()); + } + + public void test5_FromString() { + final String s = "7b6e8"; + final AbbreviatedObjectId i = AbbreviatedObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toObjectId()); + } + + public void test6_FromString() { + final String s = "7b6e80"; + final AbbreviatedObjectId i = AbbreviatedObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toObjectId()); + } + + public void test7_FromString() { + final String s = "7b6e806"; + final AbbreviatedObjectId i = AbbreviatedObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toObjectId()); + } + + public void test8_FromString() { + final String s = "7b6e8067"; + final AbbreviatedObjectId i = AbbreviatedObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toObjectId()); + } + + public void test9_FromString() { + final String s = "7b6e8067e"; + final AbbreviatedObjectId i = AbbreviatedObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toObjectId()); + } + + public void test17_FromString() { + final String s = "7b6e8067ec96acef9"; + final AbbreviatedObjectId i = AbbreviatedObjectId.fromString(s); + assertNotNull(i); + assertEquals(s.length(), i.length()); + assertFalse(i.isComplete()); + assertEquals(s, i.name()); + assertNull(i.toObjectId()); + } + + public void testEquals_Short() { + final String s = "7b6e8067"; + final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(s); + final AbbreviatedObjectId b = AbbreviatedObjectId.fromString(s); + assertNotSame(a, b); + assertTrue(a.hashCode() == b.hashCode()); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + } + + public void testEquals_Full() { + final String s = "7b6e8067ec96acef9a4184b43210d583b6d2f99a"; + final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(s); + final AbbreviatedObjectId b = AbbreviatedObjectId.fromString(s); + assertNotSame(a, b); + assertTrue(a.hashCode() == b.hashCode()); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + } + + public void testNotEquals_SameLength() { + final String sa = "7b6e8067"; + final String sb = "7b6e806e"; + final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(sa); + final AbbreviatedObjectId b = AbbreviatedObjectId.fromString(sb); + assertFalse(a.equals(b)); + assertFalse(b.equals(a)); + } + + public void testNotEquals_DiffLength() { + final String sa = "7b6e8067abcd"; + final String sb = "7b6e8067"; + final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(sa); + final AbbreviatedObjectId b = AbbreviatedObjectId.fromString(sb); + assertFalse(a.equals(b)); + assertFalse(b.equals(a)); + } + + public void testPrefixCompare_Full() { + final String s1 = "7b6e8067ec96acef9a4184b43210d583b6d2f99a"; + final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(s1); + final ObjectId i1 = ObjectId.fromString(s1); + assertEquals(0, a.prefixCompare(i1)); + assertTrue(i1.startsWith(a)); + + final String s2 = "7b6e8067ec96acef9a4184b43210d583b6d2f99b"; + final ObjectId i2 = ObjectId.fromString(s2); + assertTrue(a.prefixCompare(i2) < 0); + assertFalse(i2.startsWith(a)); + + final String s3 = "7b6e8067ec96acef9a4184b43210d583b6d2f999"; + final ObjectId i3 = ObjectId.fromString(s3); + assertTrue(a.prefixCompare(i3) > 0); + assertFalse(i3.startsWith(a)); + } + + public void testPrefixCompare_1() { + final String sa = "7"; + final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(sa); + + final String s1 = "7b6e8067ec96acef9a4184b43210d583b6d2f99a"; + final ObjectId i1 = ObjectId.fromString(s1); + assertEquals(0, a.prefixCompare(i1)); + assertTrue(i1.startsWith(a)); + + final String s2 = "8b6e8067ec96acef9a4184b43210d583b6d2f99a"; + final ObjectId i2 = ObjectId.fromString(s2); + assertTrue(a.prefixCompare(i2) < 0); + assertFalse(i2.startsWith(a)); + + final String s3 = "6b6e8067ec96acef9a4184b43210d583b6d2f99a"; + final ObjectId i3 = ObjectId.fromString(s3); + assertTrue(a.prefixCompare(i3) > 0); + assertFalse(i3.startsWith(a)); + } + + public void testPrefixCompare_7() { + final String sa = "7b6e806"; + final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(sa); + + final String s1 = "7b6e8067ec96acef9a4184b43210d583b6d2f99a"; + final ObjectId i1 = ObjectId.fromString(s1); + assertEquals(0, a.prefixCompare(i1)); + assertTrue(i1.startsWith(a)); + + final String s2 = "7b6e8167ec86acef9a4184b43210d583b6d2f99a"; + final ObjectId i2 = ObjectId.fromString(s2); + assertTrue(a.prefixCompare(i2) < 0); + assertFalse(i2.startsWith(a)); + + final String s3 = "7b6e8057eca6acef9a4184b43210d583b6d2f99a"; + final ObjectId i3 = ObjectId.fromString(s3); + assertTrue(a.prefixCompare(i3) > 0); + assertFalse(i3.startsWith(a)); + } + + public void testPrefixCompare_8() { + final String sa = "7b6e8067"; + final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(sa); + + final String s1 = "7b6e8067ec96acef9a4184b43210d583b6d2f99a"; + final ObjectId i1 = ObjectId.fromString(s1); + assertEquals(0, a.prefixCompare(i1)); + assertTrue(i1.startsWith(a)); + + final String s2 = "7b6e8167ec86acef9a4184b43210d583b6d2f99a"; + final ObjectId i2 = ObjectId.fromString(s2); + assertTrue(a.prefixCompare(i2) < 0); + assertFalse(i2.startsWith(a)); + + final String s3 = "7b6e8057eca6acef9a4184b43210d583b6d2f99a"; + final ObjectId i3 = ObjectId.fromString(s3); + assertTrue(a.prefixCompare(i3) > 0); + assertFalse(i3.startsWith(a)); + } + + public void testPrefixCompare_9() { + final String sa = "7b6e8067e"; + final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(sa); + + final String s1 = "7b6e8067ec96acef9a4184b43210d583b6d2f99a"; + final ObjectId i1 = ObjectId.fromString(s1); + assertEquals(0, a.prefixCompare(i1)); + assertTrue(i1.startsWith(a)); + + final String s2 = "7b6e8167ec86acef9a4184b43210d583b6d2f99a"; + final ObjectId i2 = ObjectId.fromString(s2); + assertTrue(a.prefixCompare(i2) < 0); + assertFalse(i2.startsWith(a)); + + final String s3 = "7b6e8057eca6acef9a4184b43210d583b6d2f99a"; + final ObjectId i3 = ObjectId.fromString(s3); + assertTrue(a.prefixCompare(i3) > 0); + assertFalse(i3.startsWith(a)); + } + + public void testPrefixCompare_17() { + final String sa = "7b6e8067ec96acef9"; + final AbbreviatedObjectId a = AbbreviatedObjectId.fromString(sa); + + final String s1 = "7b6e8067ec96acef9a4184b43210d583b6d2f99a"; + final ObjectId i1 = ObjectId.fromString(s1); + assertEquals(0, a.prefixCompare(i1)); + assertTrue(i1.startsWith(a)); + + final String s2 = "7b6e8067eca6acef9a4184b43210d583b6d2f99a"; + final ObjectId i2 = ObjectId.fromString(s2); + assertTrue(a.prefixCompare(i2) < 0); + assertFalse(i2.startsWith(a)); + + final String s3 = "7b6e8067ec86acef9a4184b43210d583b6d2f99a"; + final ObjectId i3 = ObjectId.fromString(s3); + assertTrue(a.prefixCompare(i3) > 0); + assertFalse(i3.startsWith(a)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java new file mode 100644 index 000000000..644c7b366 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConcurrentRepackTest.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2009, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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; +import java.util.Arrays; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevWalk; + +public class ConcurrentRepackTest extends RepositoryTestCase { + public void setUp() throws Exception { + WindowCacheConfig windowCacheConfig = new WindowCacheConfig(); + windowCacheConfig.setPackedGitOpenFiles(1); + WindowCache.reconfigure(windowCacheConfig); + super.setUp(); + } + + protected void tearDown() throws Exception { + super.tearDown(); + WindowCacheConfig windowCacheConfig = new WindowCacheConfig(); + WindowCache.reconfigure(windowCacheConfig); + } + + public void testObjectInNewPack() throws IncorrectObjectTypeException, + IOException { + // Create a new object in a new pack, and test that it is present. + // + final Repository eden = createNewEmptyRepo(); + final RevObject o1 = writeBlob(eden, "o1"); + pack(eden, o1); + assertEquals(o1.name(), parse(o1).name()); + } + + public void testObjectMovedToNewPack1() + throws IncorrectObjectTypeException, IOException { + // Create an object and pack it. Then remove that pack and put the + // object into a different pack file, with some other object. We + // still should be able to access the objects. + // + final Repository eden = createNewEmptyRepo(); + final RevObject o1 = writeBlob(eden, "o1"); + final File[] out1 = pack(eden, o1); + assertEquals(o1.name(), parse(o1).name()); + + final RevObject o2 = writeBlob(eden, "o2"); + pack(eden, o2, o1); + + // Force close, and then delete, the old pack. + // + whackCache(); + delete(out1); + + // Now here is the interesting thing. Will git figure the new + // object exists in the new pack, and not the old one. + // + assertEquals(o2.name(), parse(o2).name()); + assertEquals(o1.name(), parse(o1).name()); + } + + public void testObjectMovedWithinPack() + throws IncorrectObjectTypeException, IOException { + // Create an object and pack it. + // + final Repository eden = createNewEmptyRepo(); + final RevObject o1 = writeBlob(eden, "o1"); + final File[] out1 = pack(eden, o1); + assertEquals(o1.name(), parse(o1).name()); + + // Force close the old pack. + // + whackCache(); + + // Now overwrite the old pack in place. This method of creating a + // different pack under the same file name is partially broken. We + // should also have a different file name because the list of objects + // within the pack has been modified. + // + final RevObject o2 = writeBlob(eden, "o2"); + final PackWriter pw = new PackWriter(eden, NullProgressMonitor.INSTANCE); + pw.addObject(o2); + pw.addObject(o1); + write(out1, pw); + + // Try the old name, then the new name. The old name should cause the + // pack to reload when it opens and the index and pack mismatch. + // + assertEquals(o1.name(), parse(o1).name()); + assertEquals(o2.name(), parse(o2).name()); + } + + public void testObjectMovedToNewPack2() + throws IncorrectObjectTypeException, IOException { + // Create an object and pack it. Then remove that pack and put the + // object into a different pack file, with some other object. We + // still should be able to access the objects. + // + final Repository eden = createNewEmptyRepo(); + final RevObject o1 = writeBlob(eden, "o1"); + final File[] out1 = pack(eden, o1); + assertEquals(o1.name(), parse(o1).name()); + + final ObjectLoader load1 = db.openBlob(o1); + assertNotNull(load1); + + final RevObject o2 = writeBlob(eden, "o2"); + pack(eden, o2, o1); + + // Force close, and then delete, the old pack. + // + whackCache(); + delete(out1); + + // Now here is the interesting thing... can the loader we made + // earlier still resolve the object, even though its underlying + // pack is gone, but the object still exists. + // + final ObjectLoader load2 = db.openBlob(o1); + assertNotNull(load2); + assertNotSame(load1, load2); + + final byte[] data2 = load2.getCachedBytes(); + final byte[] data1 = load1.getCachedBytes(); + assertNotNull(data2); + assertNotNull(data1); + assertNotSame(data1, data2); // cache should be per-pack, not per object + assertTrue(Arrays.equals(data1, data2)); + assertEquals(load2.getType(), load1.getType()); + } + + private static void whackCache() { + final WindowCacheConfig config = new WindowCacheConfig(); + config.setPackedGitOpenFiles(1); + WindowCache.reconfigure(config); + } + + private RevObject parse(final AnyObjectId id) + throws MissingObjectException, IOException { + return new RevWalk(db).parseAny(id); + } + + private File[] pack(final Repository src, final RevObject... list) + throws IOException { + final PackWriter pw = new PackWriter(src, NullProgressMonitor.INSTANCE); + for (final RevObject o : list) { + pw.addObject(o); + } + + final ObjectId name = pw.computeName(); + final File packFile = fullPackFileName(name, ".pack"); + final File idxFile = fullPackFileName(name, ".idx"); + final File[] files = new File[] { packFile, idxFile }; + write(files, pw); + return files; + } + + private static void write(final File[] files, final PackWriter pw) + throws IOException { + final long begin = files[0].getParentFile().lastModified(); + FileOutputStream out; + + out = new FileOutputStream(files[0]); + try { + pw.writePack(out); + } finally { + out.close(); + } + + out = new FileOutputStream(files[1]); + try { + pw.writeIndex(out); + } finally { + out.close(); + } + + touch(begin, files[0].getParentFile()); + } + + private static void delete(final File[] list) { + final long begin = list[0].getParentFile().lastModified(); + for (final File f : list) { + f.delete(); + assertFalse(f + " was removed", f.exists()); + } + touch(begin, list[0].getParentFile()); + } + + private static void touch(final long begin, final File dir) { + while (begin >= dir.lastModified()) { + try { + Thread.sleep(25); + } catch (InterruptedException ie) { + // + } + dir.setLastModified(System.currentTimeMillis()); + } + } + + private File fullPackFileName(final ObjectId name, final String suffix) { + final File packdir = new File(db.getObjectsDirectory(), "pack"); + return new File(packdir, "pack-" + name.name() + suffix); + } + + private RevObject writeBlob(final Repository repo, final String data) + throws IOException { + final RevWalk revWalk = new RevWalk(repo); + final byte[] bytes = Constants.encode(data); + final ObjectWriter ow = new ObjectWriter(repo); + final ObjectId id = ow.writeBlob(bytes); + try { + parse(id); + fail("Object " + id.name() + " should not exist in test repository"); + } catch (MissingObjectException e) { + // Ok + } + return revWalk.lookupBlob(id); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConstantsEncodingTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConstantsEncodingTest.java new file mode 100644 index 000000000..96568ff23 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConstantsEncodingTest.java @@ -0,0 +1,95 @@ +/* + * 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.io.UnsupportedEncodingException; +import java.util.Arrays; + +import junit.framework.TestCase; + +public class ConstantsEncodingTest extends TestCase { + public void testEncodeASCII_SimpleASCII() + throws UnsupportedEncodingException { + final String src = "abc"; + final byte[] exp = { 'a', 'b', 'c' }; + final byte[] res = Constants.encodeASCII(src); + assertTrue(Arrays.equals(exp, res)); + assertEquals(src, new String(res, 0, res.length, "UTF-8")); + } + + public void testEncodeASCII_FailOnNonASCII() { + final String src = "ŪnÄ­cÅde̽"; + try { + Constants.encodeASCII(src); + fail("Incorrectly accepted a Unicode character"); + } catch (IllegalArgumentException err) { + assertEquals("Not ASCII string: " + src, err.getMessage()); + } + } + + public void testEncodeASCII_Number13() { + final long src = 13; + final byte[] exp = { '1', '3' }; + final byte[] res = Constants.encodeASCII(src); + assertTrue(Arrays.equals(exp, res)); + } + + public void testEncode_SimpleASCII() throws UnsupportedEncodingException { + final String src = "abc"; + final byte[] exp = { 'a', 'b', 'c' }; + final byte[] res = Constants.encode(src); + assertTrue(Arrays.equals(exp, res)); + assertEquals(src, new String(res, 0, res.length, "UTF-8")); + } + + public void testEncode_Unicode() throws UnsupportedEncodingException { + final String src = "ŪnÄ­cÅde̽"; + final byte[] exp = { (byte) 0xC5, (byte) 0xAA, 0x6E, (byte) 0xC4, + (byte) 0xAD, 0x63, (byte) 0xC5, (byte) 0x8D, 0x64, 0x65, + (byte) 0xCC, (byte) 0xBD }; + final byte[] res = Constants.encode(src); + assertTrue(Arrays.equals(exp, res)); + assertEquals(src, new String(res, 0, res.length, "UTF-8")); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java new file mode 100644 index 000000000..f871cc052 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffTest.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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; + +public class IndexDiffTest extends RepositoryTestCase { + public void testAdded() throws IOException { + GitIndex index = new GitIndex(db); + writeTrashFile("file1", "file1"); + writeTrashFile("dir/subfile", "dir/subfile"); + Tree tree = new Tree(db); + + index.add(trash, new File(trash, "file1")); + index.add(trash, new File(trash, "dir/subfile")); + IndexDiff diff = new IndexDiff(tree, index); + diff.diff(); + assertEquals(2, diff.getAdded().size()); + assertTrue(diff.getAdded().contains("file1")); + assertTrue(diff.getAdded().contains("dir/subfile")); + assertEquals(0, diff.getChanged().size()); + assertEquals(0, diff.getModified().size()); + assertEquals(0, diff.getRemoved().size()); + } + + public void testRemoved() throws IOException { + GitIndex index = new GitIndex(db); + writeTrashFile("file2", "file2"); + writeTrashFile("dir/file3", "dir/file3"); + + Tree tree = new Tree(db); + tree.addFile("file2"); + tree.addFile("dir/file3"); + assertEquals(2, tree.memberCount()); + tree.findBlobMember("file2").setId(ObjectId.fromString("30d67d4672d5c05833b7192cc77a79eaafb5c7ad")); + Tree tree2 = (Tree) tree.findTreeMember("dir"); + tree2.findBlobMember("file3").setId(ObjectId.fromString("873fb8d667d05436d728c52b1d7a09528e6eb59b")); + tree2.setId(new ObjectWriter(db).writeTree(tree2)); + tree.setId(new ObjectWriter(db).writeTree(tree)); + + IndexDiff diff = new IndexDiff(tree, index); + diff.diff(); + assertEquals(2, diff.getRemoved().size()); + assertTrue(diff.getRemoved().contains("file2")); + assertTrue(diff.getRemoved().contains("dir/file3")); + assertEquals(0, diff.getChanged().size()); + assertEquals(0, diff.getModified().size()); + assertEquals(0, diff.getAdded().size()); + } + + public void testModified() throws IOException { + GitIndex index = new GitIndex(db); + + + index.add(trash, writeTrashFile("file2", "file2")); + index.add(trash, writeTrashFile("dir/file3", "dir/file3")); + + writeTrashFile("dir/file3", "changed"); + + Tree tree = new Tree(db); + tree.addFile("file2").setId(ObjectId.fromString("0123456789012345678901234567890123456789")); + tree.addFile("dir/file3").setId(ObjectId.fromString("0123456789012345678901234567890123456789")); + assertEquals(2, tree.memberCount()); + + Tree tree2 = (Tree) tree.findTreeMember("dir"); + tree2.setId(new ObjectWriter(db).writeTree(tree2)); + tree.setId(new ObjectWriter(db).writeTree(tree)); + IndexDiff diff = new IndexDiff(tree, index); + diff.diff(); + assertEquals(2, diff.getChanged().size()); + assertTrue(diff.getChanged().contains("file2")); + assertTrue(diff.getChanged().contains("dir/file3")); + assertEquals(1, diff.getModified().size()); + assertTrue(diff.getModified().contains("dir/file3")); + assertEquals(0, diff.getAdded().size()); + assertEquals(0, diff.getRemoved().size()); + assertEquals(0, diff.getMissing().size()); + } + + public void testUnchangedSimple() throws IOException { + GitIndex index = new GitIndex(db); + + index.add(trash, writeTrashFile("a.b", "a.b")); + index.add(trash, writeTrashFile("a.c", "a.c")); + index.add(trash, writeTrashFile("a=c", "a=c")); + index.add(trash, writeTrashFile("a=d", "a=d")); + + Tree tree = new Tree(db); + // got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin + tree.addFile("a.b").setId(ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); + tree.addFile("a.c").setId(ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); + tree.addFile("a=c").setId(ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); + tree.addFile("a=d").setId(ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); + + tree.setId(new ObjectWriter(db).writeTree(tree)); + + IndexDiff diff = new IndexDiff(tree, index); + diff.diff(); + assertEquals(0, diff.getChanged().size()); + assertEquals(0, diff.getAdded().size()); + assertEquals(0, diff.getRemoved().size()); + assertEquals(0, diff.getMissing().size()); + assertEquals(0, diff.getModified().size()); + } + + /** + * This test has both files and directories that involve + * the tricky ordering used by Git. + * + * @throws IOException + */ + public void testUnchangedComplex() throws IOException { + GitIndex index = new GitIndex(db); + + index.add(trash, writeTrashFile("a.b", "a.b")); + index.add(trash, writeTrashFile("a.c", "a.c")); + index.add(trash, writeTrashFile("a/b.b/b", "a/b.b/b")); + index.add(trash, writeTrashFile("a/b", "a/b")); + index.add(trash, writeTrashFile("a/c", "a/c")); + index.add(trash, writeTrashFile("a=c", "a=c")); + index.add(trash, writeTrashFile("a=d", "a=d")); + + Tree tree = new Tree(db); + // got the hash id'd from the data using echo -n a.b|git hash-object -t blob --stdin + tree.addFile("a.b").setId(ObjectId.fromString("f6f28df96c2b40c951164286e08be7c38ec74851")); + tree.addFile("a.c").setId(ObjectId.fromString("6bc0e647512d2a0bef4f26111e484dc87df7f5ca")); + tree.addFile("a/b.b/b").setId(ObjectId.fromString("8d840bd4e2f3a48ff417c8e927d94996849933fd")); + tree.addFile("a/b").setId(ObjectId.fromString("db89c972fc57862eae378f45b74aca228037d415")); + tree.addFile("a/c").setId(ObjectId.fromString("52ad142a008aeb39694bafff8e8f1be75ed7f007")); + tree.addFile("a=c").setId(ObjectId.fromString("06022365ddbd7fb126761319633bf73517770714")); + tree.addFile("a=d").setId(ObjectId.fromString("fa6414df3da87840700e9eeb7fc261dd77ccd5c2")); + + Tree tree3 = (Tree) tree.findTreeMember("a/b.b"); + tree3.setId(new ObjectWriter(db).writeTree(tree3)); + Tree tree2 = (Tree) tree.findTreeMember("a"); + tree2.setId(new ObjectWriter(db).writeTree(tree2)); + tree.setId(new ObjectWriter(db).writeTree(tree)); + + IndexDiff diff = new IndexDiff(tree, index); + diff.diff(); + assertEquals(0, diff.getChanged().size()); + assertEquals(0, diff.getAdded().size()); + assertEquals(0, diff.getRemoved().size()); + assertEquals(0, diff.getMissing().size()); + assertEquals(0, diff.getModified().size()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexTreeWalkerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexTreeWalkerTest.java new file mode 100644 index 000000000..eb7b622d6 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexTreeWalkerTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2006, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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; + +import org.eclipse.jgit.lib.GitIndex.Entry; + +public class IndexTreeWalkerTest extends RepositoryTestCase { + private ArrayList treeOnlyEntriesVisited = new ArrayList(); + private ArrayList bothVisited = new ArrayList(); + private ArrayList indexOnlyEntriesVisited = new ArrayList(); + + private class TestIndexTreeVisitor extends AbstractIndexTreeVisitor { + public void visitEntry(TreeEntry treeEntry, Entry indexEntry, File file) { + if (treeEntry == null) + indexOnlyEntriesVisited.add(indexEntry.getName()); + else if (indexEntry == null) + treeOnlyEntriesVisited.add(treeEntry.getFullName()); + else bothVisited.add(indexEntry.getName()); + } + } + + /* + * Need to think about what I really need to be able to do.... + * + * 1) Visit all entries in index and tree + * 2) Get all directories that exist in the index, but not in the tree + * -- I'm pretty sure that I don't need to do the other way around + * because I already + */ + + public void testTreeOnlyOneLevel() throws IOException { + GitIndex index = new GitIndex(db); + Tree tree = new Tree(db); + tree.addFile("foo"); + tree.addFile("bar"); + + new IndexTreeWalker(index, tree, trash, new TestIndexTreeVisitor()).walk(); + + assertTrue(treeOnlyEntriesVisited.get(0).equals("bar")); + assertTrue(treeOnlyEntriesVisited.get(1).equals("foo")); + } + + public void testIndexOnlyOneLevel() throws IOException { + GitIndex index = new GitIndex(db); + Tree tree = new Tree(db); + + index.add(trash, writeTrashFile("foo", "foo")); + index.add(trash, writeTrashFile("bar", "bar")); + new IndexTreeWalker(index, tree, trash, new TestIndexTreeVisitor()).walk(); + + assertTrue(indexOnlyEntriesVisited.get(0).equals("bar")); + assertTrue(indexOnlyEntriesVisited.get(1).equals("foo")); + } + + public void testBoth() throws IOException { + GitIndex index = new GitIndex(db); + Tree tree = new Tree(db); + + index.add(trash, writeTrashFile("a", "a")); + tree.addFile("b/b"); + index.add(trash, writeTrashFile("c", "c")); + tree.addFile("c"); + + new IndexTreeWalker(index, tree, trash, new TestIndexTreeVisitor()).walk(); + assertTrue(indexOnlyEntriesVisited.contains("a")); + assertTrue(treeOnlyEntriesVisited.contains("b/b")); + assertTrue(bothVisited.contains("c")); + + } + + public void testIndexOnlySubDirs() throws IOException { + GitIndex index = new GitIndex(db); + Tree tree = new Tree(db); + + index.add(trash, writeTrashFile("foo/bar/baz", "foobar")); + index.add(trash, writeTrashFile("asdf", "asdf")); + new IndexTreeWalker(index, tree, trash, new TestIndexTreeVisitor()).walk(); + + assertEquals("asdf", indexOnlyEntriesVisited.get(0)); + assertEquals("foo/bar/baz", indexOnlyEntriesVisited.get(1)); + } + + public void testLeavingTree() throws IOException { + GitIndex index = new GitIndex(db); + index.add(trash, writeTrashFile("foo/bar", "foo/bar")); + index.add(trash, writeTrashFile("foobar", "foobar")); + + new IndexTreeWalker(index, db.mapTree(index.writeTree()), trash, new AbstractIndexTreeVisitor() { + @Override + public void visitEntry(TreeEntry entry, Entry indexEntry, File f) { + if (entry == null || indexEntry == null) + fail(); + } + + @Override + public void finishVisitTree(Tree tree, int i, String curDir) + throws IOException { + if (tree.memberCount() == 0) + fail(); + if (i == 0) + fail(); + } + + }).walk(); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MockSystemReader.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MockSystemReader.java new file mode 100644 index 000000000..2e94632ce --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/MockSystemReader.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2009, Robin Rosenberg + * Copyright (C) 2009, Yann Simon + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.HashMap; +import java.util.Map; +import java.util.TimeZone; + +import org.eclipse.jgit.util.SystemReader; + +class MockSystemReader extends SystemReader { + final Map values = new HashMap(); + + FileBasedConfig userGitConfig; + + MockSystemReader() { + init(Constants.OS_USER_NAME_KEY); + init(Constants.GIT_AUTHOR_NAME_KEY); + init(Constants.GIT_AUTHOR_EMAIL_KEY); + init(Constants.GIT_COMMITTER_NAME_KEY); + init(Constants.GIT_COMMITTER_EMAIL_KEY); + userGitConfig = new FileBasedConfig(null); + } + + private void init(final String n) { + values.put(n, n); + } + + public String getenv(String variable) { + return values.get(variable); + } + + public String getProperty(String key) { + return values.get(key); + } + + public FileBasedConfig openUserConfig() { + return userGitConfig; + } + + public String getHostname() { + return "fake.host.example.com"; + } + + @Override + public long getCurrentTime() { + return 1250379778668L; // Sat Aug 15 20:12:58 GMT-03:30 2009 + } + + @Override + public int getTimezone(long when) { + return TimeZone.getTimeZone("GMT-3:30").getOffset(when); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java new file mode 100644 index 000000000..7c2676bc7 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java @@ -0,0 +1,1331 @@ +/* + * Copyright (C) 2008, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 junit.framework.TestCase; + +import org.eclipse.jgit.errors.CorruptObjectException; + +public class ObjectCheckerTest extends TestCase { + private ObjectChecker checker; + + protected void setUp() throws Exception { + super.setUp(); + checker = new ObjectChecker(); + } + + public void testInvalidType() { + try { + checker.check(Constants.OBJ_BAD, new byte[0]); + fail("Did not throw CorruptObjectException"); + } catch (CorruptObjectException e) { + final String m = e.getMessage(); + assertEquals("Invalid object type: " + Constants.OBJ_BAD, m); + } + } + + public void testCheckBlob() throws CorruptObjectException { + // Any blob should pass... + checker.checkBlob(new byte[0]); + checker.checkBlob(new byte[1]); + + checker.check(Constants.OBJ_BLOB, new byte[0]); + checker.check(Constants.OBJ_BLOB, new byte[1]); + } + + public void testValidCommitNoParent() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("author A. U. Thor 1 +0000\n"); + b.append("committer A. U. Thor 1 +0000\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkCommit(data); + checker.check(Constants.OBJ_COMMIT, data); + } + + public void testValidCommitBlankAuthor() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("author <> 0 +0000\n"); + b.append("committer <> 0 +0000\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkCommit(data); + checker.check(Constants.OBJ_COMMIT, data); + } + + public void testValidCommit1Parent() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("parent "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("author A. U. Thor 1 +0000\n"); + b.append("committer A. U. Thor 1 +0000\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkCommit(data); + checker.check(Constants.OBJ_COMMIT, data); + } + + public void testValidCommit2Parent() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("parent "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("parent "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("author A. U. Thor 1 +0000\n"); + b.append("committer A. U. Thor 1 +0000\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkCommit(data); + checker.check(Constants.OBJ_COMMIT, data); + } + + public void testValidCommit128Parent() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + for (int i = 0; i < 128; i++) { + b.append("parent "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + } + + b.append("author A. U. Thor 1 +0000\n"); + b.append("committer A. U. Thor 1 +0000\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkCommit(data); + checker.check(Constants.OBJ_COMMIT, data); + } + + public void testValidCommitNormalTime() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + final String when = "1222757360 -0730"; + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("author A. U. Thor " + when + "\n"); + b.append("committer A. U. Thor " + when + "\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkCommit(data); + checker.check(Constants.OBJ_COMMIT, data); + } + + public void testInvalidCommitNoTree1() { + final StringBuilder b = new StringBuilder(); + + b.append("parent "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + assertEquals("no tree header", e.getMessage()); + } + } + + public void testInvalidCommitNoTree2() { + final StringBuilder b = new StringBuilder(); + + b.append("trie "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + assertEquals("no tree header", e.getMessage()); + } + } + + public void testInvalidCommitNoTree3() { + final StringBuilder b = new StringBuilder(); + + b.append("tree"); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + assertEquals("no tree header", e.getMessage()); + } + } + + public void testInvalidCommitNoTree4() { + final StringBuilder b = new StringBuilder(); + + b.append("tree\t"); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + assertEquals("no tree header", e.getMessage()); + } + } + + public void testInvalidCommitInvalidTree1() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("zzzzfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + assertEquals("invalid tree", e.getMessage()); + } + } + + public void testInvalidCommitInvalidTree2() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append("z\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + assertEquals("invalid tree", e.getMessage()); + } + } + + public void testInvalidCommitInvalidTree3() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9b"); + b.append("\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + assertEquals("invalid tree", e.getMessage()); + } + } + + public void testInvalidCommitInvalidTree4() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + assertEquals("invalid tree", e.getMessage()); + } + } + + public void testInvalidCommitInvalidParent1() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("parent "); + b.append("\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + assertEquals("invalid parent", e.getMessage()); + } + } + + public void testInvalidCommitInvalidParent2() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("parent "); + b.append("zzzzfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append("\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + assertEquals("invalid parent", e.getMessage()); + } + } + + public void testInvalidCommitInvalidParent3() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("parent "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append("\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + assertEquals("invalid parent", e.getMessage()); + } + } + + public void testInvalidCommitInvalidParent4() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("parent "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append("z\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + assertEquals("invalid parent", e.getMessage()); + } + } + + public void testInvalidCommitInvalidParent5() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("parent\t"); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append("\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + // Yes, really, we complain about author not being + // found as the invalid parent line wasn't consumed. + assertEquals("no author", e.getMessage()); + } + } + + public void testInvalidCommitNoAuthor() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("committer A. U. Thor 1 +0000\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + // Yes, really, we complain about author not being + // found as the invalid parent line wasn't consumed. + assertEquals("no author", e.getMessage()); + } + } + + public void testInvalidCommitNoCommitter1() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("author A. U. Thor 1 +0000\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + // Yes, really, we complain about author not being + // found as the invalid parent line wasn't consumed. + assertEquals("no committer", e.getMessage()); + } + } + + public void testInvalidCommitNoCommitter2() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("author A. U. Thor 1 +0000\n"); + b.append("\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + // Yes, really, we complain about author not being + // found as the invalid parent line wasn't consumed. + assertEquals("no committer", e.getMessage()); + } + } + + public void testInvalidCommitInvalidAuthor1() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("author A. U. Thor 1 +0000\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + // Yes, really, we complain about author not being + // found as the invalid parent line wasn't consumed. + assertEquals("invalid author", e.getMessage()); + } + } + + public void testInvalidCommitInvalidAuthor3() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("author 1 +0000\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + // Yes, really, we complain about author not being + // found as the invalid parent line wasn't consumed. + assertEquals("invalid author", e.getMessage()); + } + } + + public void testInvalidCommitInvalidAuthor4() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("author a +0000\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + // Yes, really, we complain about author not being + // found as the invalid parent line wasn't consumed. + assertEquals("invalid author", e.getMessage()); + } + } + + public void testInvalidCommitInvalidAuthor5() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("author a \n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + // Yes, really, we complain about author not being + // found as the invalid parent line wasn't consumed. + assertEquals("invalid author", e.getMessage()); + } + } + + public void testInvalidCommitInvalidAuthor6() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("author a z"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + // Yes, really, we complain about author not being + // found as the invalid parent line wasn't consumed. + assertEquals("invalid author", e.getMessage()); + } + } + + public void testInvalidCommitInvalidAuthor7() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("author a 1 z"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + // Yes, really, we complain about author not being + // found as the invalid parent line wasn't consumed. + assertEquals("invalid author", e.getMessage()); + } + } + + public void testInvalidCommitInvalidCommitter() { + final StringBuilder b = new StringBuilder(); + + b.append("tree "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("author a 1 +0000\n"); + b.append("committer a <"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + // Yes, really, we complain about author not being + // found as the invalid parent line wasn't consumed. + assertEquals("invalid committer", e.getMessage()); + } + } + + public void testValidTag() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + + b.append("object "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("type commit\n"); + b.append("tag test-tag\n"); + b.append("tagger A. U. Thor 1 +0000\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTag(data); + checker.check(Constants.OBJ_TAG, data); + } + + public void testInvalidTagNoObject1() { + final StringBuilder b = new StringBuilder(); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("no object header", e.getMessage()); + } + } + + public void testInvalidTagNoObject2() { + final StringBuilder b = new StringBuilder(); + + b.append("object\t"); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("no object header", e.getMessage()); + } + } + + public void testInvalidTagNoObject3() { + final StringBuilder b = new StringBuilder(); + + b.append("obejct "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("no object header", e.getMessage()); + } + } + + public void testInvalidTagNoObject4() { + final StringBuilder b = new StringBuilder(); + + b.append("object "); + b.append("zz9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("invalid object", e.getMessage()); + } + } + + public void testInvalidTagNoObject5() { + final StringBuilder b = new StringBuilder(); + + b.append("object "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append(" \n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("invalid object", e.getMessage()); + } + } + + public void testInvalidTagNoObject6() { + final StringBuilder b = new StringBuilder(); + + b.append("object "); + b.append("be9"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("invalid object", e.getMessage()); + } + } + + public void testInvalidTagNoType1() { + final StringBuilder b = new StringBuilder(); + + b.append("object "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("no type header", e.getMessage()); + } + } + + public void testInvalidTagNoType2() { + final StringBuilder b = new StringBuilder(); + + b.append("object "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("type\tcommit\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("no type header", e.getMessage()); + } + } + + public void testInvalidTagNoType3() { + final StringBuilder b = new StringBuilder(); + + b.append("object "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("tpye commit\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("no type header", e.getMessage()); + } + } + + public void testInvalidTagNoType4() { + final StringBuilder b = new StringBuilder(); + + b.append("object "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("type commit"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("no tag header", e.getMessage()); + } + } + + public void testInvalidTagNoTagHeader1() { + final StringBuilder b = new StringBuilder(); + + b.append("object "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("type commit\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("no tag header", e.getMessage()); + } + } + + public void testInvalidTagNoTagHeader2() { + final StringBuilder b = new StringBuilder(); + + b.append("object "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("type commit\n"); + b.append("tag\tfoo\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("no tag header", e.getMessage()); + } + } + + public void testInvalidTagNoTagHeader3() { + final StringBuilder b = new StringBuilder(); + + b.append("object "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("type commit\n"); + b.append("tga foo\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("no tag header", e.getMessage()); + } + } + + public void testInvalidTagNoTagHeader4() { + final StringBuilder b = new StringBuilder(); + + b.append("object "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("type commit\n"); + b.append("tag foo"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("no tagger header", e.getMessage()); + } + } + + public void testInvalidTagNoTaggerHeader1() { + final StringBuilder b = new StringBuilder(); + + b.append("object "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("type commit\n"); + b.append("tag foo\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("no tagger header", e.getMessage()); + } + } + + public void testInvalidTagInvalidTaggerHeader1() { + final StringBuilder b = new StringBuilder(); + + b.append("object "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("type commit\n"); + b.append("tag foo\n"); + b.append("tagger \n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("invalid tagger", e.getMessage()); + } + } + + public void testInvalidTagInvalidTaggerHeader3() { + final StringBuilder b = new StringBuilder(); + + b.append("object "); + b.append("be9bfa841874ccc9f2ef7c48d0c76226f89b7189"); + b.append('\n'); + + b.append("type commit\n"); + b.append("tag foo\n"); + b.append("tagger a < 1 +000\n"); + + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTag(data); + fail("incorrectly accepted invalid tag"); + } catch (CorruptObjectException e) { + assertEquals("invalid tagger", e.getMessage()); + } + } + + public void testValidEmptyTree() throws CorruptObjectException { + checker.checkTree(new byte[0]); + checker.check(Constants.OBJ_TREE, new byte[0]); + } + + public void testValidTree1() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + entry(b, "100644 regular-file"); + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + public void testValidTree2() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + entry(b, "100755 executable"); + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + public void testValidTree3() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + entry(b, "40000 tree"); + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + public void testValidTree4() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + entry(b, "120000 symlink"); + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + public void testValidTree5() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + entry(b, "160000 git link"); + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + public void testValidTree6() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + entry(b, "100644 .a"); + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + public void testValidTreeSorting1() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + entry(b, "100644 fooaaa"); + entry(b, "100755 foobar"); + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + public void testValidTreeSorting2() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + entry(b, "100755 fooaaa"); + entry(b, "100644 foobar"); + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + public void testValidTreeSorting3() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + entry(b, "40000 a"); + entry(b, "100644 b"); + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + public void testValidTreeSorting4() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + entry(b, "100644 a"); + entry(b, "40000 b"); + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + public void testValidTreeSorting5() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + entry(b, "100644 a.c"); + entry(b, "40000 a"); + entry(b, "100644 a0c"); + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + public void testValidTreeSorting6() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + entry(b, "40000 a"); + entry(b, "100644 apple"); + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + public void testValidTreeSorting7() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + entry(b, "40000 an orang"); + entry(b, "40000 an orange"); + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + public void testValidTreeSorting8() throws CorruptObjectException { + final StringBuilder b = new StringBuilder(); + entry(b, "100644 a"); + entry(b, "100644 a0c"); + entry(b, "100644 b"); + final byte[] data = Constants.encodeASCII(b.toString()); + checker.checkTree(data); + } + + public void testInvalidTreeModeStartsWithZero1() { + final StringBuilder b = new StringBuilder(); + entry(b, "0 a"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("mode starts with '0'", e.getMessage()); + } + } + + public void testInvalidTreeModeStartsWithZero2() { + final StringBuilder b = new StringBuilder(); + entry(b, "0100644 a"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("mode starts with '0'", e.getMessage()); + } + } + + public void testInvalidTreeModeStartsWithZero3() { + final StringBuilder b = new StringBuilder(); + entry(b, "040000 a"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("mode starts with '0'", e.getMessage()); + } + } + + public void testInvalidTreeModeNotOctal1() { + final StringBuilder b = new StringBuilder(); + entry(b, "8 a"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("invalid mode character", e.getMessage()); + } + } + + public void testInvalidTreeModeNotOctal2() { + final StringBuilder b = new StringBuilder(); + entry(b, "Z a"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("invalid mode character", e.getMessage()); + } + } + + public void testInvalidTreeModeNotSupportedMode1() { + final StringBuilder b = new StringBuilder(); + entry(b, "1 a"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("invalid mode 1", e.getMessage()); + } + } + + public void testInvalidTreeModeNotSupportedMode2() { + final StringBuilder b = new StringBuilder(); + entry(b, "170000 a"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("invalid mode " + 0170000, e.getMessage()); + } + } + + public void testInvalidTreeModeMissingName() { + final StringBuilder b = new StringBuilder(); + b.append("100644"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("truncated in mode", e.getMessage()); + } + } + + public void testInvalidTreeNameContainsSlash() { + final StringBuilder b = new StringBuilder(); + entry(b, "100644 a/b"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("name contains '/'", e.getMessage()); + } + } + + public void testInvalidTreeNameIsEmpty() { + final StringBuilder b = new StringBuilder(); + entry(b, "100644 "); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("zero length name", e.getMessage()); + } + } + + public void testInvalidTreeNameIsDot() { + final StringBuilder b = new StringBuilder(); + entry(b, "100644 ."); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("invalid name '.'", e.getMessage()); + } + } + + public void testInvalidTreeNameIsDotDot() { + final StringBuilder b = new StringBuilder(); + entry(b, "100644 .."); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("invalid name '..'", e.getMessage()); + } + } + + public void testInvalidTreeTruncatedInName() { + final StringBuilder b = new StringBuilder(); + b.append("100644 b"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("truncated in name", e.getMessage()); + } + } + + public void testInvalidTreeTruncatedInObjectId() { + final StringBuilder b = new StringBuilder(); + b.append("100644 b\0\1\2"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("truncated in object id", e.getMessage()); + } + } + + public void testInvalidTreeBadSorting1() { + final StringBuilder b = new StringBuilder(); + entry(b, "100644 foobar"); + entry(b, "100644 fooaaa"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("incorrectly sorted", e.getMessage()); + } + } + + public void testInvalidTreeBadSorting2() { + final StringBuilder b = new StringBuilder(); + entry(b, "40000 a"); + entry(b, "100644 a.c"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("incorrectly sorted", e.getMessage()); + } + } + + public void testInvalidTreeBadSorting3() { + final StringBuilder b = new StringBuilder(); + entry(b, "100644 a0c"); + entry(b, "40000 a"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("incorrectly sorted", e.getMessage()); + } + } + + public void testInvalidTreeDuplicateNames1() { + final StringBuilder b = new StringBuilder(); + entry(b, "100644 a"); + entry(b, "100644 a"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("duplicate entry names", e.getMessage()); + } + } + + public void testInvalidTreeDuplicateNames2() { + final StringBuilder b = new StringBuilder(); + entry(b, "100644 a"); + entry(b, "100755 a"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("duplicate entry names", e.getMessage()); + } + } + + public void testInvalidTreeDuplicateNames3() { + final StringBuilder b = new StringBuilder(); + entry(b, "100644 a"); + entry(b, "40000 a"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("duplicate entry names", e.getMessage()); + } + } + + public void testInvalidTreeDuplicateNames4() { + final StringBuilder b = new StringBuilder(); + entry(b, "100644 a"); + entry(b, "100644 a.c"); + entry(b, "100644 a.d"); + entry(b, "100644 a.e"); + entry(b, "40000 a"); + entry(b, "100644 zoo"); + final byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkTree(data); + fail("incorrectly accepted an invalid tree"); + } catch (CorruptObjectException e) { + assertEquals("duplicate entry names", e.getMessage()); + } + } + + private static void entry(final StringBuilder b, final String modeName) { + b.append(modeName); + b.append('\0'); + for (int i = 0; i < Constants.OBJECT_ID_LENGTH; i++) + b.append((char) i); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexTestCase.java new file mode 100644 index 000000000..31de3d98a --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexTestCase.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.util.Iterator; +import java.util.NoSuchElementException; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.PackIndex.MutableEntry; + +public abstract class PackIndexTestCase extends RepositoryTestCase { + + PackIndex smallIdx; + + PackIndex denseIdx; + + public void setUp() throws Exception { + super.setUp(); + smallIdx = PackIndex.open(getFileForPack34be9032()); + denseIdx = PackIndex.open(getFileForPackdf2982f28()); + } + + /** + * Return file with appropriate index version for prepared pack. + * + * @return file with index + */ + public abstract File getFileForPack34be9032(); + + /** + * Return file with appropriate index version for prepared pack. + * + * @return file with index + */ + public abstract File getFileForPackdf2982f28(); + + /** + * Verify CRC32 support. + * + * @throws MissingObjectException + * @throws UnsupportedOperationException + */ + public abstract void testCRC32() throws MissingObjectException, + UnsupportedOperationException; + + /** + * Test contracts of Iterator methods and this implementation remove() + * limitations. + */ + public void testIteratorMethodsContract() { + Iterator iter = smallIdx.iterator(); + while (iter.hasNext()) { + iter.next(); + } + + try { + iter.next(); + fail("next() unexpectedly returned element"); + } catch (NoSuchElementException x) { + // expected + } + + try { + iter.remove(); + fail("remove() shouldn't be implemented"); + } catch (UnsupportedOperationException x) { + // expected + } + } + + /** + * Test results of iterator comparing to content of well-known (prepared) + * small index. + */ + public void testIteratorReturnedValues1() { + Iterator iter = smallIdx.iterator(); + assertEquals("4b825dc642cb6eb9a060e54bf8d69288fbee4904", iter.next() + .name()); + assertEquals("540a36d136cf413e4b064c2b0e0a4db60f77feab", iter.next() + .name()); + assertEquals("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259", iter.next() + .name()); + assertEquals("6ff87c4664981e4397625791c8ea3bbb5f2279a3", iter.next() + .name()); + assertEquals("82c6b885ff600be425b4ea96dee75dca255b69e7", iter.next() + .name()); + assertEquals("902d5476fa249b7abc9d84c611577a81381f0327", iter.next() + .name()); + assertEquals("aabf2ffaec9b497f0950352b3e582d73035c2035", iter.next() + .name()); + assertEquals("c59759f143fb1fe21c197981df75a7ee00290799", iter.next() + .name()); + assertFalse(iter.hasNext()); + } + + /** + * Compare offset from iterator entries with output of findOffset() method. + */ + public void testCompareEntriesOffsetsWithFindOffsets() { + for (MutableEntry me : smallIdx) { + assertEquals(smallIdx.findOffset(me.toObjectId()), me.getOffset()); + } + for (MutableEntry me : denseIdx) { + assertEquals(denseIdx.findOffset(me.toObjectId()), me.getOffset()); + } + } + + /** + * Test partial results of iterator comparing to content of well-known + * (prepared) dense index, that may need multi-level indexing. + */ + public void testIteratorReturnedValues2() { + Iterator iter = denseIdx.iterator(); + while (!iter.next().name().equals( + "0a3d7772488b6b106fb62813c4d6d627918d9181")) { + // just iterating + } + assertEquals("1004d0d7ac26fbf63050a234c9b88a46075719d3", iter.next() + .name()); // same level-1 + assertEquals("10da5895682013006950e7da534b705252b03be6", iter.next() + .name()); // same level-1 + assertEquals("1203b03dc816ccbb67773f28b3c19318654b0bc8", iter.next() + .name()); + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV1Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV1Test.java new file mode 100644 index 000000000..f3082fb29 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV1Test.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2008, Imran M Yousuf + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2009, Matthias Sohn + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.util.JGitTestUtil; + +public class PackIndexV1Test extends PackIndexTestCase { + @Override + public File getFileForPack34be9032() { + return JGitTestUtil.getTestResourceFile( + "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idx"); + } + + @Override + public File getFileForPackdf2982f28() { + return JGitTestUtil.getTestResourceFile( + "pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.idx"); + } + + /** + * Verify CRC32 - V1 should not index anything. + * + * @throws MissingObjectException + */ + @Override + public void testCRC32() throws MissingObjectException { + assertFalse(smallIdx.hasCRC32Support()); + try { + smallIdx.findCRC32(ObjectId + .fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904")); + fail("index V1 shouldn't support CRC"); + } catch (UnsupportedOperationException x) { + // expected + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV2Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV2Test.java new file mode 100644 index 000000000..c5669f9d2 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackIndexV2Test.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2008, Imran M Yousuf + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2009, Matthias Sohn + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.util.JGitTestUtil; + +public class PackIndexV2Test extends PackIndexTestCase { + @Override + public File getFileForPack34be9032() { + return JGitTestUtil.getTestResourceFile( + "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idxV2"); + } + + @Override + public File getFileForPackdf2982f28() { + return JGitTestUtil.getTestResourceFile( + "pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.idxV2"); + } + + /** + * Verify CRC32 indexing. + * + * @throws UnsupportedOperationException + * @throws MissingObjectException + */ + @Override + public void testCRC32() throws MissingObjectException, + UnsupportedOperationException { + assertTrue(smallIdx.hasCRC32Support()); + assertEquals(0x00000000C2B64258l, smallIdx.findCRC32(ObjectId + .fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904"))); + assertEquals(0x0000000072AD57C2l, smallIdx.findCRC32(ObjectId + .fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"))); + assertEquals(0x00000000FF10A479l, smallIdx.findCRC32(ObjectId + .fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"))); + assertEquals(0x0000000034B27DDCl, smallIdx.findCRC32(ObjectId + .fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3"))); + assertEquals(0x000000004743F1E4l, smallIdx.findCRC32(ObjectId + .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"))); + assertEquals(0x00000000640B358Bl, smallIdx.findCRC32(ObjectId + .fromString("902d5476fa249b7abc9d84c611577a81381f0327"))); + assertEquals(0x000000002A17CB5El, smallIdx.findCRC32(ObjectId + .fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"))); + assertEquals(0x000000000B3B5BA6l, smallIdx.findCRC32(ObjectId + .fromString("c59759f143fb1fe21c197981df75a7ee00290799"))); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackReverseIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackReverseIndexTest.java new file mode 100644 index 000000000..19b705813 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackReverseIndexTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2008, Imran M Yousuf + * Copyright (C) 2008, Marek Zawirski + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.CorruptObjectException; +import org.eclipse.jgit.lib.PackIndex.MutableEntry; +import org.eclipse.jgit.util.JGitTestUtil; + +public class PackReverseIndexTest extends RepositoryTestCase { + + private PackIndex idx; + + private PackReverseIndex reverseIdx; + + /** + * Set up tested class instance, test constructor by the way. + */ + public void setUp() throws Exception { + super.setUp(); + // index with both small (< 2^31) and big offsets + idx = PackIndex.open(JGitTestUtil.getTestResourceFile( + "pack-huge.idx")); + reverseIdx = new PackReverseIndex(idx); + } + + /** + * Test findObject() for all index entries. + */ + public void testFindObject() { + for (MutableEntry me : idx) + assertEquals(me.toObjectId(), reverseIdx.findObject(me.getOffset())); + } + + /** + * Test findObject() with illegal argument. + */ + public void testFindObjectWrongOffset() { + assertNull(reverseIdx.findObject(0)); + } + + /** + * Test findNextOffset() for all index entries. + * + * @throws CorruptObjectException + */ + public void testFindNextOffset() throws CorruptObjectException { + long offset = findFirstOffset(); + assertTrue(offset > 0); + for (int i = 0; i < idx.getObjectCount(); i++) { + long newOffset = reverseIdx.findNextOffset(offset, Long.MAX_VALUE); + assertTrue(newOffset > offset); + if (i == idx.getObjectCount() - 1) + assertEquals(newOffset, Long.MAX_VALUE); + else + assertEquals(newOffset, idx.findOffset(reverseIdx + .findObject(newOffset))); + offset = newOffset; + } + } + + /** + * Test findNextOffset() with wrong illegal argument as offset. + */ + public void testFindNextOffsetWrongOffset() { + try { + reverseIdx.findNextOffset(0, Long.MAX_VALUE); + fail("findNextOffset() should throw exception"); + } catch (CorruptObjectException x) { + // expected + } + } + + private long findFirstOffset() { + long min = Long.MAX_VALUE; + for (MutableEntry me : idx) + min = Math.min(min, me.getOffset()); + return min; + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java new file mode 100644 index 000000000..703290749 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/PackWriterTest.java @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.PackIndex.MutableEntry; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.IndexPack; +import org.eclipse.jgit.util.JGitTestUtil; + +public class PackWriterTest extends RepositoryTestCase { + + private static final List EMPTY_LIST_OBJECT = Collections + . emptyList(); + + private static final List EMPTY_LIST_REVS = Collections + . emptyList(); + + private PackWriter writer; + + private ByteArrayOutputStream os; + + private PackOutputStream cos; + + private File packBase; + + private File packFile; + + private File indexFile; + + private PackFile pack; + + public void setUp() throws Exception { + super.setUp(); + os = new ByteArrayOutputStream(); + cos = new PackOutputStream(os); + packBase = new File(trash, "tmp_pack"); + packFile = new File(trash, "tmp_pack.pack"); + indexFile = new File(trash, "tmp_pack.idx"); + writer = new PackWriter(db, new TextProgressMonitor()); + } + + /** + * Test constructor for exceptions, default settings, initialization. + */ + public void testContructor() { + assertEquals(false, writer.isDeltaBaseAsOffset()); + assertEquals(true, writer.isReuseDeltas()); + assertEquals(true, writer.isReuseObjects()); + assertEquals(0, writer.getObjectsNumber()); + } + + /** + * Change default settings and verify them. + */ + public void testModifySettings() { + writer.setDeltaBaseAsOffset(true); + writer.setReuseDeltas(false); + writer.setReuseObjects(false); + + assertEquals(true, writer.isDeltaBaseAsOffset()); + assertEquals(false, writer.isReuseDeltas()); + assertEquals(false, writer.isReuseObjects()); + } + + /** + * Write empty pack by providing empty sets of interesting/uninteresting + * objects and check for correct format. + * + * @throws IOException + */ + public void testWriteEmptyPack1() throws IOException { + createVerifyOpenPack(EMPTY_LIST_OBJECT, EMPTY_LIST_OBJECT, false, false); + + assertEquals(0, writer.getObjectsNumber()); + assertEquals(0, pack.getObjectCount()); + assertEquals("da39a3ee5e6b4b0d3255bfef95601890afd80709", writer + .computeName().name()); + } + + /** + * Write empty pack by providing empty iterator of objects to write and + * check for correct format. + * + * @throws IOException + */ + public void testWriteEmptyPack2() throws IOException { + createVerifyOpenPack(EMPTY_LIST_REVS.iterator()); + + assertEquals(0, writer.getObjectsNumber()); + assertEquals(0, pack.getObjectCount()); + } + + /** + * Try to pass non-existing object as uninteresting, with non-ignoring + * setting. + * + * @throws IOException + */ + public void testNotIgnoreNonExistingObjects() throws IOException { + final ObjectId nonExisting = ObjectId + .fromString("0000000000000000000000000000000000000001"); + try { + createVerifyOpenPack(EMPTY_LIST_OBJECT, Collections.nCopies(1, + nonExisting), false, false); + fail("Should have thrown MissingObjectException"); + } catch (MissingObjectException x) { + // expected + } + } + + /** + * Try to pass non-existing object as uninteresting, with ignoring setting. + * + * @throws IOException + */ + public void testIgnoreNonExistingObjects() throws IOException { + final ObjectId nonExisting = ObjectId + .fromString("0000000000000000000000000000000000000001"); + createVerifyOpenPack(EMPTY_LIST_OBJECT, Collections.nCopies(1, + nonExisting), false, true); + // shouldn't throw anything + } + + /** + * Create pack basing on only interesting objects, then precisely verify + * content. No delta reuse here. + * + * @throws IOException + */ + public void testWritePack1() throws IOException { + writer.setReuseDeltas(false); + writeVerifyPack1(); + } + + /** + * Test writing pack without object reuse. Pack content/preparation as in + * {@link #testWritePack1()}. + * + * @throws IOException + */ + public void testWritePack1NoObjectReuse() throws IOException { + writer.setReuseDeltas(false); + writer.setReuseObjects(false); + writeVerifyPack1(); + } + + /** + * Create pack basing on both interesting and uninteresting objects, then + * precisely verify content. No delta reuse here. + * + * @throws IOException + */ + public void testWritePack2() throws IOException { + writeVerifyPack2(false); + } + + /** + * Test pack writing with deltas reuse, delta-base first rule. Pack + * content/preparation as in {@link #testWritePack2()}. + * + * @throws IOException + */ + public void testWritePack2DeltasReuseRefs() throws IOException { + writeVerifyPack2(true); + } + + /** + * Test pack writing with delta reuse. Delta bases referred as offsets. Pack + * configuration as in {@link #testWritePack2DeltasReuseRefs()}. + * + * @throws IOException + */ + public void testWritePack2DeltasReuseOffsets() throws IOException { + writer.setDeltaBaseAsOffset(true); + writeVerifyPack2(true); + } + + /** + * Test pack writing with delta reuse. Raw-data copy (reuse) is made on a + * pack with CRC32 index. Pack configuration as in + * {@link #testWritePack2DeltasReuseRefs()}. + * + * @throws IOException + */ + public void testWritePack2DeltasCRC32Copy() throws IOException { + final File packDir = new File(db.getObjectsDirectory(), "pack"); + final File crc32Pack = new File(packDir, + "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.pack"); + final File crc32Idx = new File(packDir, + "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idx"); + copyFile(JGitTestUtil.getTestResourceFile( + "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idxV2"), + crc32Idx); + db.openPack(crc32Pack, crc32Idx); + + writeVerifyPack2(true); + } + + /** + * Create pack basing on fixed objects list, then precisely verify content. + * No delta reuse here. + * + * @throws IOException + * @throws MissingObjectException + * + */ + public void testWritePack3() throws MissingObjectException, IOException { + writer.setReuseDeltas(false); + final ObjectId forcedOrder[] = new ObjectId[] { + ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"), + ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"), + ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), + ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"), + ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"), + ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") }; + final RevWalk parser = new RevWalk(db); + final RevObject forcedOrderRevs[] = new RevObject[forcedOrder.length]; + for (int i = 0; i < forcedOrder.length; i++) + forcedOrderRevs[i] = parser.parseAny(forcedOrder[i]); + + createVerifyOpenPack(Arrays.asList(forcedOrderRevs).iterator()); + + assertEquals(forcedOrder.length, writer.getObjectsNumber()); + verifyObjectsOrder(forcedOrder); + assertEquals("ed3f96b8327c7c66b0f8f70056129f0769323d86", writer + .computeName().name()); + } + + /** + * Another pack creation: basing on both interesting and uninteresting + * objects. No delta reuse possible here, as this is a specific case when we + * write only 1 commit, associated with 1 tree, 1 blob. + * + * @throws IOException + */ + public void testWritePack4() throws IOException { + writeVerifyPack4(false); + } + + /** + * Test thin pack writing: 1 blob delta base is on objects edge. Pack + * configuration as in {@link #testWritePack4()}. + * + * @throws IOException + */ + public void testWritePack4ThinPack() throws IOException { + writeVerifyPack4(true); + } + + /** + * Compare sizes of packs created using {@link #testWritePack2()} and + * {@link #testWritePack2DeltasReuseRefs()}. The pack using deltas should + * be smaller. + * + * @throws Exception + */ + public void testWritePack2SizeDeltasVsNoDeltas() throws Exception { + testWritePack2(); + final long sizePack2NoDeltas = cos.length(); + tearDown(); + setUp(); + testWritePack2DeltasReuseRefs(); + final long sizePack2DeltasRefs = cos.length(); + + assertTrue(sizePack2NoDeltas > sizePack2DeltasRefs); + } + + /** + * Compare sizes of packs created using + * {@link #testWritePack2DeltasReuseRefs()} and + * {@link #testWritePack2DeltasReuseOffsets()}. The pack with delta bases + * written as offsets should be smaller. + * + * @throws Exception + */ + public void testWritePack2SizeOffsetsVsRefs() throws Exception { + testWritePack2DeltasReuseRefs(); + final long sizePack2DeltasRefs = cos.length(); + tearDown(); + setUp(); + testWritePack2DeltasReuseOffsets(); + final long sizePack2DeltasOffsets = cos.length(); + + assertTrue(sizePack2DeltasRefs > sizePack2DeltasOffsets); + } + + /** + * Compare sizes of packs created using {@link #testWritePack4()} and + * {@link #testWritePack4ThinPack()}. Obviously, the thin pack should be + * smaller. + * + * @throws Exception + */ + public void testWritePack4SizeThinVsNoThin() throws Exception { + testWritePack4(); + final long sizePack4 = cos.length(); + tearDown(); + setUp(); + testWritePack4ThinPack(); + final long sizePack4Thin = cos.length(); + + assertTrue(sizePack4 > sizePack4Thin); + } + + public void testWriteIndex() throws Exception { + writer.setIndexVersion(2); + writeVerifyPack4(false); + + // Validate that IndexPack came up with the right CRC32 value. + final PackIndex idx1 = PackIndex.open(indexFile); + assertTrue(idx1 instanceof PackIndexV2); + assertEquals(0x4743F1E4L, idx1.findCRC32(ObjectId + .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"))); + + // Validate that an index written by PackWriter is the same. + final File idx2File = new File(indexFile.getAbsolutePath() + ".2"); + final FileOutputStream is = new FileOutputStream(idx2File); + try { + writer.writeIndex(is); + } finally { + is.close(); + } + final PackIndex idx2 = PackIndex.open(idx2File); + assertTrue(idx2 instanceof PackIndexV2); + assertEquals(idx1.getObjectCount(), idx2.getObjectCount()); + assertEquals(idx1.getOffset64Count(), idx2.getOffset64Count()); + + for (int i = 0; i < idx1.getObjectCount(); i++) { + final ObjectId id = idx1.getObjectId(i); + assertEquals(id, idx2.getObjectId(i)); + assertEquals(idx1.findOffset(id), idx2.findOffset(id)); + assertEquals(idx1.findCRC32(id), idx2.findCRC32(id)); + } + } + + // TODO: testWritePackDeltasCycle() + // TODO: testWritePackDeltasDepth() + + private void writeVerifyPack1() throws IOException { + final LinkedList interestings = new LinkedList(); + interestings.add(ObjectId + .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")); + createVerifyOpenPack(interestings, EMPTY_LIST_OBJECT, false, false); + + final ObjectId expectedOrder[] = new ObjectId[] { + ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"), + ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"), + ObjectId.fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"), + ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), + ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"), + ObjectId.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904"), + ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"), + ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") }; + + assertEquals(expectedOrder.length, writer.getObjectsNumber()); + verifyObjectsOrder(expectedOrder); + assertEquals("34be9032ac282b11fa9babdc2b2a93ca996c9c2f", writer + .computeName().name()); + } + + private void writeVerifyPack2(boolean deltaReuse) throws IOException { + writer.setReuseDeltas(deltaReuse); + final LinkedList interestings = new LinkedList(); + interestings.add(ObjectId + .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")); + final LinkedList uninterestings = new LinkedList(); + uninterestings.add(ObjectId + .fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab")); + createVerifyOpenPack(interestings, uninterestings, false, false); + + final ObjectId expectedOrder[] = new ObjectId[] { + ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"), + ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"), + ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), + ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"), + ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"), + ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") }; + if (deltaReuse) { + // objects order influenced (swapped) by delta-base first rule + ObjectId temp = expectedOrder[4]; + expectedOrder[4] = expectedOrder[5]; + expectedOrder[5] = temp; + } + assertEquals(expectedOrder.length, writer.getObjectsNumber()); + verifyObjectsOrder(expectedOrder); + assertEquals("ed3f96b8327c7c66b0f8f70056129f0769323d86", writer + .computeName().name()); + } + + private void writeVerifyPack4(final boolean thin) throws IOException { + final LinkedList interestings = new LinkedList(); + interestings.add(ObjectId + .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")); + final LinkedList uninterestings = new LinkedList(); + uninterestings.add(ObjectId + .fromString("c59759f143fb1fe21c197981df75a7ee00290799")); + createVerifyOpenPack(interestings, uninterestings, thin, false); + + final ObjectId writtenObjects[] = new ObjectId[] { + ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"), + ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), + ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") }; + assertEquals(writtenObjects.length, writer.getObjectsNumber()); + ObjectId expectedObjects[]; + if (thin) { + expectedObjects = new ObjectId[4]; + System.arraycopy(writtenObjects, 0, expectedObjects, 0, + writtenObjects.length); + expectedObjects[3] = ObjectId + .fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3"); + + } else { + expectedObjects = writtenObjects; + } + verifyObjectsOrder(expectedObjects); + assertEquals("cded4b74176b4456afa456768b2b5aafb41c44fc", writer + .computeName().name()); + } + + private void createVerifyOpenPack(final Collection interestings, + final Collection uninterestings, final boolean thin, + final boolean ignoreMissingUninteresting) + throws MissingObjectException, IOException { + writer.setThin(thin); + writer.setIgnoreMissingUninteresting(ignoreMissingUninteresting); + writer.preparePack(interestings, uninterestings); + writer.writePack(cos); + verifyOpenPack(thin); + } + + private void createVerifyOpenPack(final Iterator objectSource) + throws MissingObjectException, IOException { + writer.preparePack(objectSource); + writer.writePack(cos); + verifyOpenPack(false); + } + + private void verifyOpenPack(final boolean thin) throws IOException { + if (thin) { + final InputStream is = new ByteArrayInputStream(os.toByteArray()); + final IndexPack indexer = new IndexPack(db, is, packBase); + try { + indexer.index(new TextProgressMonitor()); + fail("indexer should grumble about missing object"); + } catch (IOException x) { + // expected + } + } + final InputStream is = new ByteArrayInputStream(os.toByteArray()); + final IndexPack indexer = new IndexPack(db, is, packBase); + indexer.setKeepEmpty(true); + indexer.setFixThin(thin); + indexer.setIndexVersion(2); + indexer.index(new TextProgressMonitor()); + pack = new PackFile(indexFile, packFile); + } + + private void verifyObjectsOrder(final ObjectId objectsOrder[]) { + final List entries = new ArrayList(); + + for (MutableEntry me : pack) { + entries.add(me.cloneEntry()); + } + Collections.sort(entries, new Comparator() { + public int compare(MutableEntry o1, MutableEntry o2) { + return Long.signum(o1.getOffset() - o2.getOffset()); + } + }); + + int i = 0; + for (MutableEntry me : entries) { + assertEquals(objectsOrder[i++].toObjectId(), me.toObjectId()); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java new file mode 100644 index 000000000..5ac75d804 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReadTreeTest.java @@ -0,0 +1,581 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2008-2009, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; + +import org.eclipse.jgit.errors.CheckoutConflictException; + +public class ReadTreeTest extends RepositoryTestCase { + + private Tree theHead; + private Tree theMerge; + private GitIndex theIndex; + private WorkDirCheckout theReadTree; + // Each of these rules are from the read-tree manpage + // go there to see what they mean. + // Rule 0 is left out for obvious reasons :) + public void testRules1thru3_NoIndexEntry() throws IOException { + GitIndex index = new GitIndex(db); + + Tree head = new Tree(db); + FileTreeEntry headFile = head.addFile("foo"); + ObjectId objectId = ObjectId.fromString("ba78e065e2c261d4f7b8f42107588051e87e18e9"); + headFile.setId(objectId); + Tree merge = new Tree(db); + + WorkDirCheckout readTree = new WorkDirCheckout(db, trash, head, index, merge); + readTree.prescanTwoTrees(); + + assertTrue(readTree.removed.contains("foo")); + + readTree = new WorkDirCheckout(db, trash, merge, index, head); + readTree.prescanTwoTrees(); + + assertEquals(objectId, readTree.updated.get("foo")); + + ObjectId anotherId = ObjectId.fromString("ba78e065e2c261d4f7b8f42107588051e87e18ee"); + merge.addFile("foo").setId(anotherId); + + readTree = new WorkDirCheckout(db, trash, head, index, merge); + readTree.prescanTwoTrees(); + + assertEquals(anotherId, readTree.updated.get("foo")); + } + + void setupCase(HashMap headEntries, + HashMap mergeEntries, + HashMap indexEntries) throws IOException { + theHead = buildTree(headEntries); + theMerge = buildTree(mergeEntries); + theIndex = buildIndex(indexEntries); + } + + private GitIndex buildIndex(HashMap indexEntries) throws IOException { + GitIndex index = new GitIndex(db); + + if (indexEntries == null) + return index; + for (java.util.Map.Entry e : indexEntries.entrySet()) { + index.add(trash, writeTrashFile(e.getKey(), e.getValue())).forceRecheck(); + } + + return index; + } + + private Tree buildTree(HashMap headEntries) throws IOException { + Tree tree = new Tree(db); + + if (headEntries == null) + return tree; + for (java.util.Map.Entry e : headEntries.entrySet()) { + tree.addFile(e.getKey()).setId(genSha1(e.getValue())); + } + + return tree; + } + + ObjectId genSha1(String data) { + InputStream is = new ByteArrayInputStream(data.getBytes()); + ObjectWriter objectWriter = new ObjectWriter(db); + try { + return objectWriter.writeObject(Constants.OBJ_BLOB, data + .getBytes().length, is, true); + } catch (IOException e) { + fail(e.toString()); + } + return null; + } + + private WorkDirCheckout go() throws IOException { + theReadTree = new WorkDirCheckout(db, trash, theHead, theIndex, theMerge); + theReadTree.prescanTwoTrees(); + return theReadTree; + } + + // for these rules, they all have clean yes/no options + // but it doesn't matter if the entry is clean or not + // so we can just ignore the state in the filesystem entirely + public void testRules4thru13_IndexEntryNotInHead() throws IOException { + // rules 4 and 5 + HashMap idxMap; + + idxMap = new HashMap(); + idxMap.put("foo", "foo"); + setupCase(null, null, idxMap); + theReadTree = go(); + + assertTrue(theReadTree.updated.isEmpty()); + assertTrue(theReadTree.removed.isEmpty()); + assertTrue(theReadTree.conflicts.isEmpty()); + + // rules 6 and 7 + idxMap = new HashMap(); + idxMap.put("foo", "foo"); + setupCase(null, idxMap, idxMap); + theReadTree = go(); + + assertAllEmpty(); + + // rules 8 and 9 + HashMap mergeMap; + mergeMap = new HashMap(); + + mergeMap.put("foo", "merge"); + setupCase(null, mergeMap, idxMap); + go(); + + assertTrue(theReadTree.updated.isEmpty()); + assertTrue(theReadTree.removed.isEmpty()); + assertTrue(theReadTree.conflicts.contains("foo")); + + // rule 10 + + HashMap headMap = new HashMap(); + headMap.put("foo", "foo"); + setupCase(headMap, null, idxMap); + go(); + + assertTrue(theReadTree.removed.contains("foo")); + assertTrue(theReadTree.updated.isEmpty()); + assertTrue(theReadTree.conflicts.isEmpty()); + + // rule 11 + setupCase(headMap, null, idxMap); + new File(trash, "foo").delete(); + writeTrashFile("foo", "bar"); + theIndex.getMembers()[0].forceRecheck(); + go(); + + assertTrue(theReadTree.removed.isEmpty()); + assertTrue(theReadTree.updated.isEmpty()); + assertTrue(theReadTree.conflicts.contains("foo")); + + // rule 12 & 13 + headMap.put("foo", "head"); + setupCase(headMap, null, idxMap); + go(); + + assertTrue(theReadTree.removed.isEmpty()); + assertTrue(theReadTree.updated.isEmpty()); + assertTrue(theReadTree.conflicts.contains("foo")); + + // rules 14 & 15 + setupCase(headMap, headMap, idxMap); + go(); + + assertAllEmpty(); + + // rules 16 & 17 + setupCase(headMap, mergeMap, idxMap); go(); + assertTrue(theReadTree.conflicts.contains("foo")); + + // rules 18 & 19 + setupCase(headMap, idxMap, idxMap); go(); + assertAllEmpty(); + + // rule 20 + setupCase(idxMap, mergeMap, idxMap); go(); + assertTrue(theReadTree.updated.containsKey("foo")); + + // rules 21 + setupCase(idxMap, mergeMap, idxMap); + new File(trash, "foo").delete(); + writeTrashFile("foo", "bar"); + theIndex.getMembers()[0].forceRecheck(); + go(); + assertTrue(theReadTree.conflicts.contains("foo")); + } + + private void assertAllEmpty() { + assertTrue(theReadTree.removed.isEmpty()); + assertTrue(theReadTree.updated.isEmpty()); + assertTrue(theReadTree.conflicts.isEmpty()); + } + + public void testDirectoryFileSimple() throws IOException { + theIndex = new GitIndex(db); + theIndex.add(trash, writeTrashFile("DF", "DF")); + Tree treeDF = db.mapTree(theIndex.writeTree()); + + recursiveDelete(new File(trash, "DF")); + theIndex = new GitIndex(db); + theIndex.add(trash, writeTrashFile("DF/DF", "DF/DF")); + Tree treeDFDF = db.mapTree(theIndex.writeTree()); + + theIndex = new GitIndex(db); + recursiveDelete(new File(trash, "DF")); + + theIndex.add(trash, writeTrashFile("DF", "DF")); + theReadTree = new WorkDirCheckout(db, trash, treeDF, theIndex, treeDFDF); + theReadTree.prescanTwoTrees(); + + assertTrue(theReadTree.removed.contains("DF")); + assertTrue(theReadTree.updated.containsKey("DF/DF")); + + recursiveDelete(new File(trash, "DF")); + theIndex = new GitIndex(db); + theIndex.add(trash, writeTrashFile("DF/DF", "DF/DF")); + + theReadTree = new WorkDirCheckout(db, trash, treeDFDF, theIndex, treeDF); + theReadTree.prescanTwoTrees(); + assertTrue(theReadTree.removed.contains("DF/DF")); + assertTrue(theReadTree.updated.containsKey("DF")); + } + + /* + * Directory/File Conflict cases: + * It's entirely possible that in practice a number of these may be equivalent + * to the cases described in git-read-tree.txt. As long as it does the right thing, + * that's all I care about. These are basically reverse-engineered from + * what git currently does. If there are tests for these in git, it's kind of + * hard to track them all down... + * + * H I M Clean H==M H==I I==M Result + * ------------------------------------------------------------------ + *1 D D F Y N Y N Update + *2 D D F N N Y N Conflict + *3 D F D Y N N Update + *4 D F D N N N Update + *5 D F F Y N N Y Keep + *6 D F F N N N Y Keep + *7 F D F Y Y N N Update + *8 F D F N Y N N Conflict + *9 F D F Y N N N Update + *10 F D D N N Y Keep + *11 F D D N N N Conflict + *12 F F D Y N Y N Update + *13 F F D N N Y N Conflict + *14 F F D N N N Conflict + *15 0 F D N N N Conflict + *16 0 D F Y N N N Update + *17 0 D F N N N Conflict + *18 F 0 D Update + *19 D 0 F Update + */ + + public void testDirectoryFileConflicts_1() throws Exception { + // 1 + doit(mk("DF/DF"), mk("DF"), mk("DF/DF")); + assertNoConflicts(); + assertUpdated("DF"); + assertRemoved("DF/DF"); + } + + public void testDirectoryFileConflicts_2() throws Exception { + // 2 + setupCase(mk("DF/DF"), mk("DF"), mk("DF/DF")); + writeTrashFile("DF/DF", "different"); + go(); + assertConflict("DF/DF"); + + } + + public void testDirectoryFileConflicts_3() throws Exception { + // 3 - the first to break! + doit(mk("DF/DF"), mk("DF/DF"), mk("DF")); + assertUpdated("DF/DF"); + assertRemoved("DF"); + } + + public void testDirectoryFileConflicts_4() throws Exception { + // 4 (basically same as 3, just with H and M different) + doit(mk("DF/DF"), mkmap("DF/DF", "foo"), mk("DF")); + assertUpdated("DF/DF"); + assertRemoved("DF"); + + } + + public void testDirectoryFileConflicts_5() throws Exception { + // 5 + doit(mk("DF/DF"), mk("DF"), mk("DF")); + assertRemoved("DF/DF"); + + } + + public void testDirectoryFileConflicts_6() throws Exception { + // 6 + setupCase(mk("DF/DF"), mk("DF"), mk("DF")); + writeTrashFile("DF", "different"); + go(); + assertRemoved("DF/DF"); + } + + public void testDirectoryFileConflicts_7() throws Exception { + // 7 + doit(mk("DF"), mk("DF"), mk("DF/DF")); + assertUpdated("DF"); + assertRemoved("DF/DF"); + + cleanUpDF(); + setupCase(mk("DF/DF"), mk("DF/DF"), mk("DF/DF/DF/DF/DF")); + go(); + assertRemoved("DF/DF/DF/DF/DF"); + assertUpdated("DF/DF"); + + cleanUpDF(); + setupCase(mk("DF/DF"), mk("DF/DF"), mk("DF/DF/DF/DF/DF")); + writeTrashFile("DF/DF/DF/DF/DF", "diff"); + go(); + assertConflict("DF/DF/DF/DF/DF"); + assertUpdated("DF/DF"); + + } + + // 8 ? + + public void testDirectoryFileConflicts_9() throws Exception { + // 9 + doit(mk("DF"), mkmap("DF", "QP"), mk("DF/DF")); + assertRemoved("DF/DF"); + assertUpdated("DF"); + } + + public void testDirectoryFileConflicts_10() throws Exception { + // 10 + cleanUpDF(); + doit(mk("DF"), mk("DF/DF"), mk("DF/DF")); + assertNoConflicts(); + + } + + public void testDirectoryFileConflicts_11() throws Exception { + // 11 + doit(mk("DF"), mk("DF/DF"), mkmap("DF/DF", "asdf")); + assertConflict("DF/DF"); + } + + public void testDirectoryFileConflicts_12() throws Exception { + // 12 + cleanUpDF(); + doit(mk("DF"), mk("DF/DF"), mk("DF")); + assertRemoved("DF"); + assertUpdated("DF/DF"); + } + + public void testDirectoryFileConflicts_13() throws Exception { + // 13 + cleanUpDF(); + setupCase(mk("DF"), mk("DF/DF"), mk("DF")); + writeTrashFile("DF", "asdfsdf"); + go(); + assertConflict("DF"); + assertUpdated("DF/DF"); + } + + public void testDirectoryFileConflicts_14() throws Exception { + // 14 + cleanUpDF(); + doit(mk("DF"), mk("DF/DF"), mkmap("DF", "Foo")); + assertConflict("DF"); + assertUpdated("DF/DF"); + } + + public void testDirectoryFileConflicts_15() throws Exception { + // 15 + doit(mkmap(), mk("DF/DF"), mk("DF")); + assertRemoved("DF"); + assertUpdated("DF/DF"); + } + + public void testDirectoryFileConflicts_15b() throws Exception { + // 15, take 2, just to check multi-leveled + doit(mkmap(), mk("DF/DF/DF/DF"), mk("DF")); + assertRemoved("DF"); + assertUpdated("DF/DF/DF/DF"); + } + + public void testDirectoryFileConflicts_16() throws Exception { + // 16 + cleanUpDF(); + doit(mkmap(), mk("DF"), mk("DF/DF/DF")); + assertRemoved("DF/DF/DF"); + assertUpdated("DF"); + } + + public void testDirectoryFileConflicts_17() throws Exception { + // 17 + cleanUpDF(); + setupCase(mkmap(), mk("DF"), mk("DF/DF/DF")); + writeTrashFile("DF/DF/DF", "asdf"); + go(); + assertConflict("DF/DF/DF"); + assertUpdated("DF"); + } + + public void testDirectoryFileConflicts_18() throws Exception { + // 18 + cleanUpDF(); + doit(mk("DF/DF"), mk("DF/DF/DF/DF"), null); + assertRemoved("DF/DF"); + assertUpdated("DF/DF/DF/DF"); + } + + public void testDirectoryFileConflicts_19() throws Exception { + // 19 + cleanUpDF(); + doit(mk("DF/DF/DF/DF"), mk("DF/DF/DF"), null); + assertRemoved("DF/DF/DF/DF"); + assertUpdated("DF/DF/DF"); + } + + private void cleanUpDF() throws Exception { + tearDown(); + setUp(); + recursiveDelete(new File(trash, "DF")); + } + + private void assertConflict(String s) { + assertTrue(theReadTree.conflicts.contains(s)); + } + + private void assertUpdated(String s) { + assertTrue(theReadTree.updated.containsKey(s)); + } + + private void assertRemoved(String s) { + assertTrue(theReadTree.removed.contains(s)); + } + + private void assertNoConflicts() { + assertTrue(theReadTree.conflicts.isEmpty()); + } + + private void doit(HashMap h, HashMapm, + HashMap i) throws IOException { + setupCase(h, m, i); + go(); + } + + private static HashMap mk(String a) { + return mkmap(a, a); + } + + private static HashMap mkmap(String... args) { + if ((args.length % 2) > 0) + throw new IllegalArgumentException("needs to be pairs"); + + HashMap map = new HashMap(); + for (int i = 0; i < args.length; i += 2) { + map.put(args[i], args[i+1]); + } + + return map; + } + + public void testUntrackedConflicts() throws IOException { + setupCase(null, mk("foo"), null); + writeTrashFile("foo", "foo"); + go(); + + assertConflict("foo"); + + recursiveDelete(new File(trash, "foo")); + setupCase(null, mk("foo"), null); + writeTrashFile("foo/bar/baz", ""); + writeTrashFile("foo/blahblah", ""); + go(); + + assertConflict("foo/bar/baz"); + assertConflict("foo/blahblah"); + + recursiveDelete(new File(trash, "foo")); + + setupCase(mkmap("foo/bar", "", "foo/baz", ""), + mk("foo"), mkmap("foo/bar", "", "foo/baz", "")); + assertTrue(new File(trash, "foo/bar").exists()); + go(); + + assertNoConflicts(); + } + + public void testCloseNameConflictsX0() throws IOException { + setupCase(mkmap("a/a", "a/a-c"), mkmap("a/a","a/a", "b.b/b.b","b.b/b.bs"), mkmap("a/a", "a/a-c") ); + checkout(); + go(); + assertNoConflicts(); + } + + public void testCloseNameConflicts1() throws IOException { + setupCase(mkmap("a/a", "a/a-c"), mkmap("a/a","a/a", "a.a/a.a","a.a/a.a"), mkmap("a/a", "a/a-c") ); + checkout(); + go(); + assertNoConflicts(); + } + + private void checkout() throws IOException { + theReadTree = new WorkDirCheckout(db, trash, theHead, theIndex, theMerge); + theReadTree.checkout(); + } + + public void testCheckoutOutChanges() throws IOException { + setupCase(mk("foo"), mk("foo/bar"), mk("foo")); + checkout(); + + assertFalse(new File(trash, "foo").isFile()); + assertTrue(new File(trash, "foo/bar").isFile()); + recursiveDelete(new File(trash, "foo")); + + setupCase(mk("foo/bar"), mk("foo"), mk("foo/bar")); + checkout(); + + assertFalse(new File(trash, "foo/bar").isFile()); + assertTrue(new File(trash, "foo").isFile()); + + setupCase(mk("foo"), mkmap("foo", "qux"), mkmap("foo", "bar")); + + try { + checkout(); + fail("did not throw exception"); + } catch (CheckoutConflictException e) { + // should have thrown + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java new file mode 100644 index 000000000..d7b6193ce --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2009, Robin Rosenberg + * Copyright (C) 2009, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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; +import java.util.Map; + +import org.eclipse.jgit.lib.Ref.Storage; +import org.eclipse.jgit.lib.RefUpdate.Result; + +/** + * Misc tests for refs. A lot of things are tested elsewhere so not having a + * test for a ref related method, does not mean it is untested. + */ +public class RefTest extends RepositoryTestCase { + + public void testReadAllIncludingSymrefs() throws Exception { + ObjectId masterId = db.resolve("refs/heads/master"); + RefUpdate updateRef = db.updateRef("refs/remotes/origin/master"); + updateRef.setNewObjectId(masterId); + updateRef.setForceUpdate(true); + updateRef.update(); + db + .writeSymref("refs/remotes/origin/HEAD", + "refs/remotes/origin/master"); + + ObjectId r = db.resolve("refs/remotes/origin/HEAD"); + assertEquals(masterId, r); + + Map allRefs = db.getAllRefs(); + Ref refHEAD = allRefs.get("refs/remotes/origin/HEAD"); + assertNotNull(refHEAD); + assertEquals(masterId, refHEAD.getObjectId()); + assertTrue(refHEAD.isPeeled()); + assertNull(refHEAD.getPeeledObjectId()); + + Ref refmaster = allRefs.get("refs/remotes/origin/master"); + assertEquals(masterId, refmaster.getObjectId()); + assertFalse(refmaster.isPeeled()); + assertNull(refmaster.getPeeledObjectId()); + } + + public void testReadSymRefToPacked() throws IOException { + db.writeSymref("HEAD", "refs/heads/b"); + Ref ref = db.getRef("HEAD"); + assertEquals(Ref.Storage.LOOSE_PACKED, ref.getStorage()); + } + + public void testReadSymRefToLoosePacked() throws IOException { + ObjectId pid = db.resolve("refs/heads/master^"); + RefUpdate updateRef = db.updateRef("refs/heads/master"); + updateRef.setNewObjectId(pid); + updateRef.setForceUpdate(true); + Result update = updateRef.update(); + assertEquals(Result.FORCED, update); // internal + + db.writeSymref("HEAD", "refs/heads/master"); + Ref ref = db.getRef("HEAD"); + assertEquals(Ref.Storage.LOOSE_PACKED, ref.getStorage()); + } + + public void testReadLooseRef() throws IOException { + RefUpdate updateRef = db.updateRef("ref/heads/new"); + updateRef.setNewObjectId(db.resolve("refs/heads/master")); + Result update = updateRef.update(); + assertEquals(Result.NEW, update); + Ref ref = db.getRef("ref/heads/new"); + assertEquals(Storage.LOOSE, ref.getStorage()); + } + + /** + * Let an "outsider" create a loose ref with the same name as a packed one + * + * @throws IOException + * @throws InterruptedException + */ + public void testReadLoosePackedRef() throws IOException, + InterruptedException { + Ref ref = db.getRef("refs/heads/master"); + assertEquals(Storage.PACKED, ref.getStorage()); + FileOutputStream os = new FileOutputStream(new File(db.getDirectory(), + "refs/heads/master")); + os.write(ref.getObjectId().name().getBytes()); + os.write('\n'); + os.close(); + + ref = db.getRef("refs/heads/master"); + assertEquals(Storage.LOOSE_PACKED, ref.getStorage()); + } + + /** + * Modify a packed ref using the API. This creates a loose ref too, ie. + * LOOSE_PACKED + * + * @throws IOException + */ + public void testReadSimplePackedRefSameRepo() throws IOException { + Ref ref = db.getRef("refs/heads/master"); + ObjectId pid = db.resolve("refs/heads/master^"); + assertEquals(Storage.PACKED, ref.getStorage()); + RefUpdate updateRef = db.updateRef("refs/heads/master"); + updateRef.setNewObjectId(pid); + updateRef.setForceUpdate(true); + Result update = updateRef.update(); + assertEquals(Result.FORCED, update); + + ref = db.getRef("refs/heads/master"); + assertEquals(Storage.LOOSE_PACKED, ref.getStorage()); + } + + public void testOrigResolvedNamesBranch() throws IOException { + Ref ref = db.getRef("a"); + assertEquals("refs/heads/a", ref.getName()); + assertEquals("refs/heads/a", ref.getOrigName()); + } + + public void testOrigResolvedNamesSymRef() throws IOException { + Ref ref = db.getRef("HEAD"); + assertEquals("refs/heads/master", ref.getName()); + assertEquals("HEAD", ref.getOrigName()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java new file mode 100644 index 000000000..3704c5617 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefUpdateTest.java @@ -0,0 +1,768 @@ +/* + * Copyright (C) 2008, Charles O'Farrell + * Copyright (C) 2008-2009, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; + +public class RefUpdateTest extends RepositoryTestCase { + + private RefUpdate updateRef(final String name) throws IOException { + final RefUpdate ref = db.updateRef(name); + ref.setNewObjectId(db.resolve(Constants.HEAD)); + return ref; + } + + private void delete(final RefUpdate ref, final Result expected) + throws IOException { + delete(ref, expected, true, true); + } + + private void delete(final RefUpdate ref, final Result expected, + final boolean exists, final boolean removed) throws IOException { + assertEquals(exists, db.getAllRefs().containsKey(ref.getName())); + assertEquals(expected, ref.delete()); + assertEquals(!removed, db.getAllRefs().containsKey(ref.getName())); + } + + public void testNoCacheObjectIdSubclass() throws IOException { + final String newRef = "refs/heads/abc"; + final RefUpdate ru = updateRef(newRef); + final RevCommit newid = new RevCommit(ru.getNewObjectId()) { + // empty + }; + ru.setNewObjectId(newid); + Result update = ru.update(); + assertEquals(Result.NEW, update); + final Ref r = db.getAllRefs().get(newRef); + assertNotNull(r); + assertEquals(newRef, r.getName()); + assertNotNull(r.getObjectId()); + assertNotSame(newid, r.getObjectId()); + assertSame(ObjectId.class, r.getObjectId().getClass()); + assertEquals(newid.copy(), r.getObjectId()); + List reverseEntries1 = db.getReflogReader("refs/heads/abc").getReverseEntries(); + org.eclipse.jgit.lib.ReflogReader.Entry entry1 = reverseEntries1.get(0); + assertEquals(1, reverseEntries1.size()); + assertEquals(ObjectId.zeroId(), entry1.getOldId()); + assertEquals(r.getObjectId(), entry1.getNewId()); + assertEquals(new PersonIdent(db).toString(), entry1.getWho().toString()); + assertEquals("", entry1.getComment()); + List reverseEntries2 = db.getReflogReader("HEAD").getReverseEntries(); + assertEquals(0, reverseEntries2.size()); + } + + public void testNewNamespaceConflictWithLoosePrefixNameExists() + throws IOException { + final String newRef = "refs/heads/z"; + final RefUpdate ru = updateRef(newRef); + final RevCommit newid = new RevCommit(ru.getNewObjectId()) { + // empty + }; + ru.setNewObjectId(newid); + Result update = ru.update(); + assertEquals(Result.NEW, update); + // end setup + final String newRef2 = "refs/heads/z/a"; + final RefUpdate ru2 = updateRef(newRef2); + final RevCommit newid2 = new RevCommit(ru2.getNewObjectId()) { + // empty + }; + ru.setNewObjectId(newid2); + Result update2 = ru2.update(); + assertEquals(Result.LOCK_FAILURE, update2); + assertEquals(1, db.getReflogReader("refs/heads/z").getReverseEntries().size()); + assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); + } + + public void testNewNamespaceConflictWithPackedPrefixNameExists() + throws IOException { + final String newRef = "refs/heads/master/x"; + final RefUpdate ru = updateRef(newRef); + final RevCommit newid = new RevCommit(ru.getNewObjectId()) { + // empty + }; + ru.setNewObjectId(newid); + Result update = ru.update(); + assertEquals(Result.LOCK_FAILURE, update); + assertNull(db.getReflogReader("refs/heads/master/x")); + assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); + } + + public void testNewNamespaceConflictWithLoosePrefixOfExisting() + throws IOException { + final String newRef = "refs/heads/z/a"; + final RefUpdate ru = updateRef(newRef); + final RevCommit newid = new RevCommit(ru.getNewObjectId()) { + // empty + }; + ru.setNewObjectId(newid); + Result update = ru.update(); + assertEquals(Result.NEW, update); + // end setup + final String newRef2 = "refs/heads/z"; + final RefUpdate ru2 = updateRef(newRef2); + final RevCommit newid2 = new RevCommit(ru2.getNewObjectId()) { + // empty + }; + ru.setNewObjectId(newid2); + Result update2 = ru2.update(); + assertEquals(Result.LOCK_FAILURE, update2); + assertEquals(1, db.getReflogReader("refs/heads/z/a").getReverseEntries().size()); + assertNull(db.getReflogReader("refs/heads/z")); + assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); + } + + public void testNewNamespaceConflictWithPackedPrefixOfExisting() + throws IOException { + final String newRef = "refs/heads/prefix"; + final RefUpdate ru = updateRef(newRef); + final RevCommit newid = new RevCommit(ru.getNewObjectId()) { + // empty + }; + ru.setNewObjectId(newid); + Result update = ru.update(); + assertEquals(Result.LOCK_FAILURE, update); + assertNull(db.getReflogReader("refs/heads/prefix")); + assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); + } + + /** + * Delete a ref that is pointed to by HEAD + * + * @throws IOException + */ + public void testDeleteHEADreferencedRef() throws IOException { + ObjectId pid = db.resolve("refs/heads/master^"); + RefUpdate updateRef = db.updateRef("refs/heads/master"); + updateRef.setNewObjectId(pid); + updateRef.setForceUpdate(true); + Result update = updateRef.update(); + assertEquals(Result.FORCED, update); // internal + + RefUpdate updateRef2 = db.updateRef("refs/heads/master"); + Result delete = updateRef2.delete(); + assertEquals(Result.REJECTED_CURRENT_BRANCH, delete); + assertEquals(pid, db.resolve("refs/heads/master")); + assertEquals(1,db.getReflogReader("refs/heads/master").getReverseEntries().size()); + assertEquals(0,db.getReflogReader("HEAD").getReverseEntries().size()); + } + + public void testLooseDelete() throws IOException { + final String newRef = "refs/heads/abc"; + RefUpdate ref = updateRef(newRef); + ref.update(); // create loose ref + ref = updateRef(newRef); // refresh + delete(ref, Result.NO_CHANGE); + assertNull(db.getReflogReader("refs/heads/abc")); + } + + public void testDeleteHead() throws IOException { + final RefUpdate ref = updateRef(Constants.HEAD); + delete(ref, Result.REJECTED_CURRENT_BRANCH, true, false); + assertEquals(0, db.getReflogReader("refs/heads/master").getReverseEntries().size()); + assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); + } + + /** + * Delete a loose ref and make sure the directory in refs is deleted too, + * and the reflog dir too + * + * @throws IOException + */ + public void testDeleteLooseAndItsDirectory() throws IOException { + ObjectId pid = db.resolve("refs/heads/c^"); + RefUpdate updateRef = db.updateRef("refs/heads/z/c"); + updateRef.setNewObjectId(pid); + updateRef.setForceUpdate(true); + updateRef.setRefLogMessage("new test ref", false); + Result update = updateRef.update(); + assertEquals(Result.NEW, update); // internal + assertTrue(new File(db.getDirectory(), Constants.R_HEADS + "z") + .exists()); + assertTrue(new File(db.getDirectory(), "logs/refs/heads/z").exists()); + + // The real test here + RefUpdate updateRef2 = db.updateRef("refs/heads/z/c"); + updateRef2.setForceUpdate(true); + Result delete = updateRef2.delete(); + assertEquals(Result.FORCED, delete); + assertNull(db.resolve("refs/heads/z/c")); + assertFalse(new File(db.getDirectory(), Constants.R_HEADS + "z") + .exists()); + assertFalse(new File(db.getDirectory(), "logs/refs/heads/z").exists()); + } + + public void testDeleteNotFound() throws IOException { + final RefUpdate ref = updateRef("refs/heads/xyz"); + delete(ref, Result.NEW, false, true); + } + + public void testDeleteFastForward() throws IOException { + final RefUpdate ref = updateRef("refs/heads/a"); + delete(ref, Result.FAST_FORWARD); + } + + public void testDeleteForce() throws IOException { + final RefUpdate ref = db.updateRef("refs/heads/b"); + ref.setNewObjectId(db.resolve("refs/heads/a")); + delete(ref, Result.REJECTED, true, false); + ref.setForceUpdate(true); + delete(ref, Result.FORCED); + } + + public void testRefKeySameAsOrigName() { + Map allRefs = db.getAllRefs(); + for (Entry e : allRefs.entrySet()) { + assertEquals(e.getKey(), e.getValue().getOrigName()); + + } + } + + /** + * Try modify a ref forward, fast forward + * + * @throws IOException + */ + public void testUpdateRefForward() throws IOException { + ObjectId ppid = db.resolve("refs/heads/master^"); + ObjectId pid = db.resolve("refs/heads/master"); + + RefUpdate updateRef = db.updateRef("refs/heads/master"); + updateRef.setNewObjectId(ppid); + updateRef.setForceUpdate(true); + Result update = updateRef.update(); + assertEquals(Result.FORCED, update); + assertEquals(ppid, db.resolve("refs/heads/master")); + + // real test + RefUpdate updateRef2 = db.updateRef("refs/heads/master"); + updateRef2.setNewObjectId(pid); + Result update2 = updateRef2.update(); + assertEquals(Result.FAST_FORWARD, update2); + assertEquals(pid, db.resolve("refs/heads/master")); + } + + /** + * Delete a ref that exists both as packed and loose. Make sure the ref + * cannot be resolved after delete. + * + * @throws IOException + */ + public void testDeleteLoosePacked() throws IOException { + ObjectId pid = db.resolve("refs/heads/c^"); + RefUpdate updateRef = db.updateRef("refs/heads/c"); + updateRef.setNewObjectId(pid); + updateRef.setForceUpdate(true); + Result update = updateRef.update(); + assertEquals(Result.FORCED, update); // internal + + // The real test here + RefUpdate updateRef2 = db.updateRef("refs/heads/c"); + updateRef2.setForceUpdate(true); + Result delete = updateRef2.delete(); + assertEquals(Result.FORCED, delete); + assertNull(db.resolve("refs/heads/c")); + } + + /** + * Try modify a ref to same + * + * @throws IOException + */ + public void testUpdateRefNoChange() throws IOException { + ObjectId pid = db.resolve("refs/heads/master"); + RefUpdate updateRef = db.updateRef("refs/heads/master"); + updateRef.setNewObjectId(pid); + Result update = updateRef.update(); + assertEquals(Result.NO_CHANGE, update); + assertEquals(pid, db.resolve("refs/heads/master")); + } + + /** + * Test case originating from + * bug 285991 + * + * Make sure the in memory cache is updated properly after + * update of symref. This one did not fail because the + * ref was packed due to implementation issues. + * + * @throws Exception + */ + public void testRefsCacheAfterUpdate() throws Exception { + // Do not use the defalt repo for this case. + Map allRefs = db.getAllRefs(); + ObjectId oldValue = db.resolve("HEAD"); + ObjectId newValue = db.resolve("HEAD^"); + // first make HEAD refer to loose ref + RefUpdate updateRef = db.updateRef(Constants.HEAD); + updateRef.setForceUpdate(true); + updateRef.setNewObjectId(newValue); + Result update = updateRef.update(); + assertEquals(Result.FORCED, update); + + // now update that ref + updateRef = db.updateRef(Constants.HEAD); + updateRef.setForceUpdate(true); + updateRef.setNewObjectId(oldValue); + update = updateRef.update(); + assertEquals(Result.FAST_FORWARD, update); + allRefs = db.getAllRefs(); + assertEquals("refs/heads/master", allRefs.get("refs/heads/master").getName()); + assertEquals("refs/heads/master", allRefs.get("refs/heads/master").getOrigName()); + assertEquals("refs/heads/master", allRefs.get("HEAD").getName()); + assertEquals("HEAD", allRefs.get("HEAD").getOrigName()); + } + + /** + * Test case originating from + * bug 285991 + * + * Make sure the in memory cache is updated properly after + * update of symref. + * + * @throws Exception + */ + public void testRefsCacheAfterUpdateLoosOnly() throws Exception { + // Do not use the defalt repo for this case. + Map allRefs = db.getAllRefs(); + ObjectId oldValue = db.resolve("HEAD"); + db.writeSymref(Constants.HEAD, "refs/heads/newref"); + RefUpdate updateRef = db.updateRef(Constants.HEAD); + updateRef.setForceUpdate(true); + updateRef.setNewObjectId(oldValue); + Result update = updateRef.update(); + assertEquals(Result.NEW, update); + allRefs = db.getAllRefs(); + assertEquals("refs/heads/newref", allRefs.get("HEAD").getName()); + assertEquals("HEAD", allRefs.get("HEAD").getOrigName()); + assertEquals("refs/heads/newref", allRefs.get("refs/heads/newref").getName()); + assertEquals("refs/heads/newref", allRefs.get("refs/heads/newref").getOrigName()); + } + + /** + * Try modify a ref, but get wrong expected old value + * + * @throws IOException + */ + public void testUpdateRefLockFailureWrongOldValue() throws IOException { + ObjectId pid = db.resolve("refs/heads/master"); + RefUpdate updateRef = db.updateRef("refs/heads/master"); + updateRef.setNewObjectId(pid); + updateRef.setExpectedOldObjectId(db.resolve("refs/heads/master^")); + Result update = updateRef.update(); + assertEquals(Result.LOCK_FAILURE, update); + assertEquals(pid, db.resolve("refs/heads/master")); + } + + /** + * Try modify a ref forward, fast forward, checking old value first + * + * @throws IOException + */ + public void testUpdateRefForwardWithCheck1() throws IOException { + ObjectId ppid = db.resolve("refs/heads/master^"); + ObjectId pid = db.resolve("refs/heads/master"); + + RefUpdate updateRef = db.updateRef("refs/heads/master"); + updateRef.setNewObjectId(ppid); + updateRef.setForceUpdate(true); + Result update = updateRef.update(); + assertEquals(Result.FORCED, update); + assertEquals(ppid, db.resolve("refs/heads/master")); + + // real test + RefUpdate updateRef2 = db.updateRef("refs/heads/master"); + updateRef2.setExpectedOldObjectId(ppid); + updateRef2.setNewObjectId(pid); + Result update2 = updateRef2.update(); + assertEquals(Result.FAST_FORWARD, update2); + assertEquals(pid, db.resolve("refs/heads/master")); + } + + /** + * Try modify a ref forward, fast forward, checking old commit first + * + * @throws IOException + */ + public void testUpdateRefForwardWithCheck2() throws IOException { + ObjectId ppid = db.resolve("refs/heads/master^"); + ObjectId pid = db.resolve("refs/heads/master"); + + RefUpdate updateRef = db.updateRef("refs/heads/master"); + updateRef.setNewObjectId(ppid); + updateRef.setForceUpdate(true); + Result update = updateRef.update(); + assertEquals(Result.FORCED, update); + assertEquals(ppid, db.resolve("refs/heads/master")); + + // real test + RevCommit old = new RevWalk(db).parseCommit(ppid); + RefUpdate updateRef2 = db.updateRef("refs/heads/master"); + updateRef2.setExpectedOldObjectId(old); + updateRef2.setNewObjectId(pid); + Result update2 = updateRef2.update(); + assertEquals(Result.FAST_FORWARD, update2); + assertEquals(pid, db.resolve("refs/heads/master")); + } + + /** + * Try modify a ref that is locked + * + * @throws IOException + */ + public void testUpdateRefLockFailureLocked() throws IOException { + ObjectId opid = db.resolve("refs/heads/master"); + ObjectId pid = db.resolve("refs/heads/master^"); + RefUpdate updateRef = db.updateRef("refs/heads/master"); + updateRef.setNewObjectId(pid); + LockFile lockFile1 = new LockFile(new File(db.getDirectory(),"refs/heads/master")); + try { + assertTrue(lockFile1.lock()); // precondition to test + Result update = updateRef.update(); + assertEquals(Result.LOCK_FAILURE, update); + assertEquals(opid, db.resolve("refs/heads/master")); + LockFile lockFile2 = new LockFile(new File(db.getDirectory(),"refs/heads/master")); + assertFalse(lockFile2.lock()); // was locked, still is + } finally { + lockFile1.unlock(); + } + } + + /** + * Try to delete a ref. Delete requires force. + * + * @throws IOException + */ + public void testDeleteLoosePackedRejected() throws IOException { + ObjectId pid = db.resolve("refs/heads/c^"); + ObjectId oldpid = db.resolve("refs/heads/c"); + RefUpdate updateRef = db.updateRef("refs/heads/c"); + updateRef.setNewObjectId(pid); + Result update = updateRef.update(); + assertEquals(Result.REJECTED, update); + assertEquals(oldpid, db.resolve("refs/heads/c")); + } + + public void testRenameBranchNoPreviousLog() throws IOException { + assertFalse("precondition, no log on old branchg", new File(db + .getDirectory(), "logs/refs/heads/b").exists()); + ObjectId rb = db.resolve("refs/heads/b"); + ObjectId oldHead = db.resolve(Constants.HEAD); + assertFalse(rb.equals(oldHead)); // assumption for this test + RefRename renameRef = db.renameRef("refs/heads/b", + "refs/heads/new/name"); + Result result = renameRef.rename(); + assertEquals(Result.RENAMED, result); + assertEquals(rb, db.resolve("refs/heads/new/name")); + assertNull(db.resolve("refs/heads/b")); + assertEquals(1, db.getReflogReader("new/name").getReverseEntries().size()); + assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name") + .getLastEntry().getComment()); + assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists()); + assertEquals(oldHead, db.resolve(Constants.HEAD)); // unchanged + } + + public void testRenameBranchHasPreviousLog() throws IOException { + ObjectId rb = db.resolve("refs/heads/b"); + ObjectId oldHead = db.resolve(Constants.HEAD); + assertFalse("precondition for this test, branch b != HEAD", rb + .equals(oldHead)); + RefLogWriter.writeReflog(db, rb, rb, "Just a message", "refs/heads/b"); + assertTrue("no log on old branch", new File(db.getDirectory(), + "logs/refs/heads/b").exists()); + RefRename renameRef = db.renameRef("refs/heads/b", + "refs/heads/new/name"); + Result result = renameRef.rename(); + assertEquals(Result.RENAMED, result); + assertEquals(rb, db.resolve("refs/heads/new/name")); + assertNull(db.resolve("refs/heads/b")); + assertEquals(2, db.getReflogReader("new/name").getReverseEntries().size()); + assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name") + .getLastEntry().getComment()); + assertEquals("Just a message", db.getReflogReader("new/name") + .getReverseEntries().get(1).getComment()); + assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists()); + assertEquals(oldHead, db.resolve(Constants.HEAD)); // unchanged + } + + public void testRenameCurrentBranch() throws IOException { + ObjectId rb = db.resolve("refs/heads/b"); + db.writeSymref(Constants.HEAD, "refs/heads/b"); + ObjectId oldHead = db.resolve(Constants.HEAD); + assertTrue("internal test condition, b == HEAD", rb.equals(oldHead)); + RefLogWriter.writeReflog(db, rb, rb, "Just a message", "refs/heads/b"); + assertTrue("no log on old branch", new File(db.getDirectory(), + "logs/refs/heads/b").exists()); + RefRename renameRef = db.renameRef("refs/heads/b", + "refs/heads/new/name"); + Result result = renameRef.rename(); + assertEquals(Result.RENAMED, result); + assertEquals(rb, db.resolve("refs/heads/new/name")); + assertNull(db.resolve("refs/heads/b")); + assertEquals("Branch: renamed b to new/name", db.getReflogReader( + "new/name").getLastEntry().getComment()); + assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists()); + assertEquals(rb, db.resolve(Constants.HEAD)); + assertEquals(2, db.getReflogReader("new/name").getReverseEntries().size()); + assertEquals("Branch: renamed b to new/name", db.getReflogReader("new/name").getReverseEntries().get(0).getComment()); + assertEquals("Just a message", db.getReflogReader("new/name").getReverseEntries().get(1).getComment()); + } + + public void testRenameBranchAlsoInPack() throws IOException { + ObjectId rb = db.resolve("refs/heads/b"); + ObjectId rb2 = db.resolve("refs/heads/b~1"); + assertEquals(Ref.Storage.PACKED, db.getRef("refs/heads/b").getStorage()); + RefUpdate updateRef = db.updateRef("refs/heads/b"); + updateRef.setNewObjectId(rb2); + updateRef.setForceUpdate(true); + Result update = updateRef.update(); + assertEquals("internal check new ref is loose", Result.FORCED, update); + assertEquals(Ref.Storage.LOOSE_PACKED, db.getRef("refs/heads/b") + .getStorage()); + RefLogWriter.writeReflog(db, rb, rb, "Just a message", "refs/heads/b"); + assertTrue("no log on old branch", new File(db.getDirectory(), + "logs/refs/heads/b").exists()); + RefRename renameRef = db.renameRef("refs/heads/b", + "refs/heads/new/name"); + Result result = renameRef.rename(); + assertEquals(Result.RENAMED, result); + assertEquals(rb2, db.resolve("refs/heads/new/name")); + assertNull(db.resolve("refs/heads/b")); + assertEquals("Branch: renamed b to new/name", db.getReflogReader( + "new/name").getLastEntry().getComment()); + assertEquals(3, db.getReflogReader("refs/heads/new/name").getReverseEntries().size()); + assertEquals("Branch: renamed b to new/name", db.getReflogReader("refs/heads/new/name").getReverseEntries().get(0).getComment()); + assertEquals(0, db.getReflogReader("HEAD").getReverseEntries().size()); + // make sure b's log file is gone too. + assertFalse(new File(db.getDirectory(), "logs/refs/heads/b").exists()); + + // Create new Repository instance, to reread caches and make sure our + // assumptions are persistent. + Repository ndb = new Repository(db.getDirectory()); + assertEquals(rb2, ndb.resolve("refs/heads/new/name")); + assertNull(ndb.resolve("refs/heads/b")); + } + + public void tryRenameWhenLocked(String toLock, String fromName, + String toName, String headPointsTo) throws IOException { + // setup + db.writeSymref(Constants.HEAD, headPointsTo); + ObjectId oldfromId = db.resolve(fromName); + ObjectId oldHeadId = db.resolve(Constants.HEAD); + RefLogWriter.writeReflog(db, oldfromId, oldfromId, "Just a message", + fromName); + List oldFromLog = db + .getReflogReader(fromName).getReverseEntries(); + List oldHeadLog = oldHeadId != null ? db + .getReflogReader(Constants.HEAD).getReverseEntries() : null; + + assertTrue("internal check, we have a log", new File(db.getDirectory(), + "logs/" + fromName).exists()); + + // "someone" has branch X locked + LockFile lockFile = new LockFile(new File(db.getDirectory(), toLock)); + try { + assertTrue(lockFile.lock()); + + // Now this is our test + RefRename renameRef = db.renameRef(fromName, toName); + Result result = renameRef.rename(); + assertEquals(Result.LOCK_FAILURE, result); + + // Check that the involved refs are the same despite the failure + assertExists(false, toName); + if (!toLock.equals(toName)) + assertExists(false, toName + ".lock"); + assertExists(true, toLock + ".lock"); + if (!toLock.equals(fromName)) + assertExists(false, "logs/" + fromName + ".lock"); + assertExists(false, "logs/" + toName + ".lock"); + assertEquals(oldHeadId, db.resolve(Constants.HEAD)); + assertEquals(oldfromId, db.resolve(fromName)); + assertNull(db.resolve(toName)); + assertEquals(oldFromLog.toString(), db.getReflogReader(fromName) + .getReverseEntries().toString()); + if (oldHeadId != null) + assertEquals(oldHeadLog, db.getReflogReader(Constants.HEAD) + .getReverseEntries()); + } finally { + lockFile.unlock(); + } + } + + private void assertExists(boolean positive, String toName) { + assertEquals(toName + (positive ? " " : " does not ") + "exist", + positive, new File(db.getDirectory(), toName).exists()); + } + + public void testRenameBranchCannotLockAFileHEADisFromLockHEAD() + throws IOException { + tryRenameWhenLocked("HEAD", "refs/heads/b", "refs/heads/new/name", + "refs/heads/b"); + } + + public void testRenameBranchCannotLockAFileHEADisFromLockFrom() + throws IOException { + tryRenameWhenLocked("refs/heads/b", "refs/heads/b", + "refs/heads/new/name", "refs/heads/b"); + } + + public void testRenameBranchCannotLockAFileHEADisFromLockTo() + throws IOException { + tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b", + "refs/heads/new/name", "refs/heads/b"); + } + + public void testRenameBranchCannotLockAFileHEADisToLockFrom() + throws IOException { + tryRenameWhenLocked("refs/heads/b", "refs/heads/b", + "refs/heads/new/name", "refs/heads/new/name"); + } + + public void testRenameBranchCannotLockAFileHEADisToLockTo() + throws IOException { + tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b", + "refs/heads/new/name", "refs/heads/new/name"); + } + + public void testRenameBranchCannotLockAFileHEADisToLockTmp() + throws IOException { + tryRenameWhenLocked("RENAMED-REF.." + Thread.currentThread().getId(), + "refs/heads/b", "refs/heads/new/name", "refs/heads/new/name"); + } + + public void testRenameBranchCannotLockAFileHEADisOtherLockFrom() + throws IOException { + tryRenameWhenLocked("refs/heads/b", "refs/heads/b", + "refs/heads/new/name", "refs/heads/a"); + } + + public void testRenameBranchCannotLockAFileHEADisOtherLockTo() + throws IOException { + tryRenameWhenLocked("refs/heads/new/name", "refs/heads/b", + "refs/heads/new/name", "refs/heads/a"); + } + + public void testRenameBranchCannotLockAFileHEADisOtherLockTmp() + throws IOException { + tryRenameWhenLocked("RENAMED-REF.." + Thread.currentThread().getId(), + "refs/heads/b", "refs/heads/new/name", "refs/heads/a"); + } + + public void testRenameRefNameColission1avoided() throws IOException { + // setup + ObjectId rb = db.resolve("refs/heads/b"); + db.writeSymref(Constants.HEAD, "refs/heads/a"); + RefUpdate updateRef = db.updateRef("refs/heads/a"); + updateRef.setNewObjectId(rb); + updateRef.setRefLogMessage("Setup", false); + assertEquals(Result.FAST_FORWARD, updateRef.update()); + ObjectId oldHead = db.resolve(Constants.HEAD); + assertTrue(rb.equals(oldHead)); // assumption for this test + RefLogWriter.writeReflog(db, rb, rb, "Just a message", "refs/heads/a"); + assertTrue("internal check, we have a log", new File(db.getDirectory(), + "logs/refs/heads/a").exists()); + + // Now this is our test + RefRename renameRef = db.renameRef("refs/heads/a", "refs/heads/a/b"); + Result result = renameRef.rename(); + assertEquals(Result.RENAMED, result); + assertNull(db.resolve("refs/heads/a")); + assertEquals(rb, db.resolve("refs/heads/a/b")); + assertEquals(3, db.getReflogReader("a/b").getReverseEntries().size()); + assertEquals("Branch: renamed a to a/b", db.getReflogReader("a/b") + .getReverseEntries().get(0).getComment()); + assertEquals("Just a message", db.getReflogReader("a/b") + .getReverseEntries().get(1).getComment()); + assertEquals("Setup", db.getReflogReader("a/b").getReverseEntries() + .get(2).getComment()); + // same thing was logged to HEAD + assertEquals("Branch: renamed a to a/b", db.getReflogReader("HEAD") + .getReverseEntries().get(0).getComment()); + } + + public void testRenameRefNameColission2avoided() throws IOException { + // setup + ObjectId rb = db.resolve("refs/heads/b"); + db.writeSymref(Constants.HEAD, "refs/heads/prefix/a"); + RefUpdate updateRef = db.updateRef("refs/heads/prefix/a"); + updateRef.setNewObjectId(rb); + updateRef.setRefLogMessage("Setup", false); + updateRef.setForceUpdate(true); + assertEquals(Result.FORCED, updateRef.update()); + ObjectId oldHead = db.resolve(Constants.HEAD); + assertTrue(rb.equals(oldHead)); // assumption for this test + RefLogWriter.writeReflog(db, rb, rb, "Just a message", + "refs/heads/prefix/a"); + assertTrue("internal check, we have a log", new File(db.getDirectory(), + "logs/refs/heads/prefix/a").exists()); + + // Now this is our test + RefRename renameRef = db.renameRef("refs/heads/prefix/a", + "refs/heads/prefix"); + Result result = renameRef.rename(); + assertEquals(Result.RENAMED, result); + + assertNull(db.resolve("refs/heads/prefix/a")); + assertEquals(rb, db.resolve("refs/heads/prefix")); + assertEquals(3, db.getReflogReader("prefix").getReverseEntries().size()); + assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader( + "prefix").getReverseEntries().get(0).getComment()); + assertEquals("Just a message", db.getReflogReader("prefix") + .getReverseEntries().get(1).getComment()); + assertEquals("Setup", db.getReflogReader("prefix").getReverseEntries() + .get(2).getComment()); + assertEquals("Branch: renamed prefix/a to prefix", db.getReflogReader( + "HEAD").getReverseEntries().get(0).getComment()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogReaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogReaderTest.java new file mode 100644 index 000000000..dae7cb895 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ReflogReaderTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2009, Robin Rosenberg + * Copyright (C) 2009, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.List; + +import org.eclipse.jgit.lib.ReflogReader.Entry; + +public class ReflogReaderTest extends RepositoryTestCase { + + static byte[] oneLine = "da85355dfc525c9f6f3927b876f379f46ccf826e 3e7549db262d1e836d9bf0af7e22355468f1717c A O Thor Too 1243028200 +0200\tcommit: Add a toString for debugging to RemoteRefUpdate\n" + .getBytes(); + + static byte[] twoLine = ("0000000000000000000000000000000000000000 c6734895958052a9dbc396cff4459dc1a25029ab A U Thor 1243028201 -0100\tbranch: Created from rr/renamebranchv4\n" + + "c6734895958052a9dbc396cff4459dc1a25029ab 54794942a18a237c57a80719afed44bb78172b10 Same A U Thor 1243028202 +0100\trebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d\n") + .getBytes(); + + static byte[] twoLineWithAppendInProgress = ("0000000000000000000000000000000000000000 c6734895958052a9dbc396cff4459dc1a25029ab A U Thor 1243028201 -0100\tbranch: Created from rr/renamebranchv4\n" + + "c6734895958052a9dbc396cff4459dc1a25029ab 54794942a18a237c57a80719afed44bb78172b10 Same A U Thor 1243028202 +0100\trebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d\n" + + "54794942a18a237c57a80719afed44bb78172b10 ") + .getBytes(); + + static byte[] aLine = "1111111111111111111111111111111111111111 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor 1243028201 -0100\tbranch: change to a\n" + .getBytes(); + + static byte[] masterLine = "2222222222222222222222222222222222222222 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor 1243028201 -0100\tbranch: change to master\n" + .getBytes(); + + static byte[] headLine = "3333333333333333333333333333333333333333 3e7549db262d1e836d9bf0af7e22355468f1717c A U Thor 1243028201 -0100\tbranch: change to HEAD\n" + .getBytes(); + + public void testReadOneLine() throws Exception { + setupReflog("logs/refs/heads/master", oneLine); + + ReflogReader reader = new ReflogReader(db, "refs/heads/master"); + Entry e = reader.getLastEntry(); + assertEquals(ObjectId + .fromString("da85355dfc525c9f6f3927b876f379f46ccf826e"), e + .getOldId()); + assertEquals(ObjectId + .fromString("3e7549db262d1e836d9bf0af7e22355468f1717c"), e + .getNewId()); + assertEquals("A O Thor Too", e.getWho().getName()); + assertEquals("authortoo@wri.tr", e.getWho().getEmailAddress()); + assertEquals(120, e.getWho().getTimeZoneOffset()); + assertEquals("2009-05-22T23:36:40", iso(e.getWho())); + assertEquals("commit: Add a toString for debugging to RemoteRefUpdate", + e.getComment()); + } + + private String iso(PersonIdent id) { + final SimpleDateFormat fmt; + fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + fmt.setTimeZone(id.getTimeZone()); + return fmt.format(id.getWhen()); + } + + public void testReadTwoLine() throws Exception { + setupReflog("logs/refs/heads/master", twoLine); + + ReflogReader reader = new ReflogReader(db, "refs/heads/master"); + List reverseEntries = reader.getReverseEntries(); + assertEquals(2, reverseEntries.size()); + Entry e = reverseEntries.get(0); + assertEquals(ObjectId + .fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e + .getOldId()); + assertEquals(ObjectId + .fromString("54794942a18a237c57a80719afed44bb78172b10"), e + .getNewId()); + assertEquals("Same A U Thor", e.getWho().getName()); + assertEquals("same.author@example.com", e.getWho().getEmailAddress()); + assertEquals(60, e.getWho().getTimeZoneOffset()); + assertEquals("2009-05-22T22:36:42", iso(e.getWho())); + assertEquals( + "rebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d", + e.getComment()); + + e = reverseEntries.get(1); + assertEquals(ObjectId + .fromString("0000000000000000000000000000000000000000"), e + .getOldId()); + assertEquals(ObjectId + .fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e + .getNewId()); + assertEquals("A U Thor", e.getWho().getName()); + assertEquals("thor@committer.au", e.getWho().getEmailAddress()); + assertEquals(-60, e.getWho().getTimeZoneOffset()); + assertEquals("2009-05-22T20:36:41", iso(e.getWho())); + assertEquals("branch: Created from rr/renamebranchv4", e.getComment()); + } + + public void testReadWhileAppendIsInProgress() throws Exception { + setupReflog("logs/refs/heads/master", twoLineWithAppendInProgress); + ReflogReader reader = new ReflogReader(db, "refs/heads/master"); + List reverseEntries = reader.getReverseEntries(); + assertEquals(2, reverseEntries.size()); + Entry e = reverseEntries.get(0); + assertEquals(ObjectId + .fromString("c6734895958052a9dbc396cff4459dc1a25029ab"), e + .getOldId()); + assertEquals(ObjectId + .fromString("54794942a18a237c57a80719afed44bb78172b10"), e + .getNewId()); + assertEquals("Same A U Thor", e.getWho().getName()); + assertEquals("same.author@example.com", e.getWho().getEmailAddress()); + assertEquals(60, e.getWho().getTimeZoneOffset()); + assertEquals("2009-05-22T22:36:42", iso(e.getWho())); + assertEquals( + "rebase finished: refs/heads/rr/renamebranch5 onto c6e3b9fe2da0293f11eae202ec35fb343191a82d", + e.getComment()); + // while similar to testReadTwoLine, we can assume that if we get the last entry + // right, everything else is too + } + + + public void testReadRightLog() throws Exception { + setupReflog("logs/refs/heads/a", aLine); + setupReflog("logs/refs/heads/master", masterLine); + setupReflog("logs/HEAD", headLine); + assertEquals("branch: change to master", db.getReflogReader("master") + .getLastEntry().getComment()); + assertEquals("branch: change to a", db.getReflogReader("a") + .getLastEntry().getComment()); + assertEquals("branch: change to HEAD", db.getReflogReader("HEAD") + .getLastEntry().getComment()); + } + + public void testNoLog() throws Exception { + assertEquals(0, db.getReflogReader("master").getReverseEntries().size()); + assertNull(db.getReflogReader("master").getLastEntry()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java new file mode 100644 index 000000000..c0591755f --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryCacheTest.java @@ -0,0 +1,132 @@ +/* + * 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 org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.RepositoryCache.FileKey; + +public class RepositoryCacheTest extends RepositoryTestCase { + public void testNonBareFileKey() { + File gitdir = db.getDirectory(); + File parent = gitdir.getParentFile(); + File other = new File(parent, "notagit"); + assertEquals(gitdir, FileKey.exact(gitdir).getFile()); + assertEquals(parent, FileKey.exact(parent).getFile()); + assertEquals(other, FileKey.exact(other).getFile()); + + assertEquals(gitdir, FileKey.lenient(gitdir).getFile()); + assertEquals(gitdir, FileKey.lenient(parent).getFile()); + assertEquals(other, FileKey.lenient(other).getFile()); + } + + public void testBareFileKey() throws IOException { + Repository bare = createNewEmptyRepo(true); + File gitdir = bare.getDirectory(); + File parent = gitdir.getParentFile(); + String name = gitdir.getName(); + assertTrue(name.endsWith(".git")); + name = name.substring(0, name.length() - 4); + + assertEquals(gitdir, FileKey.exact(gitdir).getFile()); + + assertEquals(gitdir, FileKey.lenient(gitdir).getFile()); + assertEquals(gitdir, FileKey.lenient(new File(parent, name)).getFile()); + } + + public void testFileKeyOpenExisting() throws IOException { + Repository r; + + r = new FileKey(db.getDirectory()).open(true); + assertNotNull(r); + assertEquals(db.getDirectory(), r.getDirectory()); + r.close(); + + r = new FileKey(db.getDirectory()).open(false); + assertNotNull(r); + assertEquals(db.getDirectory(), r.getDirectory()); + r.close(); + } + + public void testFileKeyOpenNew() throws IOException { + final Repository n = createNewEmptyRepo(true); + final File gitdir = n.getDirectory(); + n.close(); + recursiveDelete(gitdir); + assertFalse(gitdir.exists()); + + try { + new FileKey(gitdir).open(true); + fail("incorrectly opened a non existant repository"); + } catch (RepositoryNotFoundException e) { + assertEquals("repository not found: " + gitdir, e.getMessage()); + } + + final Repository o = new FileKey(gitdir).open(false); + assertNotNull(o); + assertEquals(gitdir, o.getDirectory()); + assertFalse(gitdir.exists()); + } + + public void testCacheRegisterOpen() throws Exception { + final File dir = db.getDirectory(); + RepositoryCache.register(db); + assertSame(db, RepositoryCache.open(FileKey.exact(dir))); + + assertEquals(".git", dir.getName()); + final File parent = dir.getParentFile(); + assertSame(db, RepositoryCache.open(FileKey.lenient(parent))); + } + + public void testCacheOpen() throws Exception { + final FileKey loc = FileKey.exact(db.getDirectory()); + final Repository d2 = RepositoryCache.open(loc); + assertNotSame(db, d2); + assertSame(d2, RepositoryCache.open(FileKey.exact(loc.getFile()))); + d2.close(); + d2.close(); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryConfigTest.java new file mode 100644 index 000000000..08e701ab5 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryConfigTest.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 java.util.LinkedList; + +import junit.framework.TestCase; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.util.SystemReader; + +/** + * Test reading of git config + */ +public class RepositoryConfigTest extends TestCase { + public void test001_ReadBareKey() throws ConfigInvalidException { + final Config c = parse("[foo]\nbar\n"); + assertEquals(true, c.getBoolean("foo", null, "bar", false)); + assertEquals("", c.getString("foo", null, "bar")); + } + + public void test002_ReadWithSubsection() throws ConfigInvalidException { + final Config c = parse("[foo \"zip\"]\nbar\n[foo \"zap\"]\nbar=false\nn=3\n"); + assertEquals(true, c.getBoolean("foo", "zip", "bar", false)); + assertEquals("", c.getString("foo","zip", "bar")); + assertEquals(false, c.getBoolean("foo", "zap", "bar", true)); + assertEquals("false", c.getString("foo", "zap", "bar")); + assertEquals(3, c.getInt("foo", "zap", "n", 4)); + assertEquals(4, c.getInt("foo", "zap","m", 4)); + } + + public void test003_PutRemote() { + final Config c = new Config(); + c.setString("sec", "ext", "name", "value"); + c.setString("sec", "ext", "name2", "value2"); + final String expText = "[sec \"ext\"]\n\tname = value\n\tname2 = value2\n"; + assertEquals(expText, c.toText()); + } + + public void test004_PutGetSimple() { + Config c = new Config(); + c.setString("my", null, "somename", "false"); + assertEquals("false", c.getString("my", null, "somename")); + assertEquals("[my]\n\tsomename = false\n", c.toText()); + } + + public void test005_PutGetStringList() { + Config c = new Config(); + final LinkedList values = new LinkedList(); + values.add("value1"); + values.add("value2"); + c.setStringList("my", null, "somename", values); + + final Object[] expArr = values.toArray(); + final String[] actArr = c.getStringList("my", null, "somename"); + assertTrue(Arrays.equals(expArr, actArr)); + + final String expText = "[my]\n\tsomename = value1\n\tsomename = value2\n"; + assertEquals(expText, c.toText()); + } + + public void test006_readCaseInsensitive() throws ConfigInvalidException { + final Config c = parse("[Foo]\nBar\n"); + assertEquals(true, c.getBoolean("foo", null, "bar", false)); + assertEquals("", c.getString("foo", null, "bar")); + } + + public void test007_readUserConfig() { + final MockSystemReader mockSystemReader = new MockSystemReader(); + SystemReader.setInstance(mockSystemReader); + final String hostname = mockSystemReader.getHostname(); + final Config userGitConfig = mockSystemReader.userGitConfig; + final Config localConfig = new Config(userGitConfig); + mockSystemReader.values.clear(); + + String authorName; + String authorEmail; + + // no values defined nowhere + authorName = localConfig.get(UserConfig.KEY).getAuthorName(); + authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail(); + assertEquals(Constants.UNKNOWN_USER_DEFAULT, authorName); + assertEquals(Constants.UNKNOWN_USER_DEFAULT + "@" + hostname, authorEmail); + + // the system user name is defined + mockSystemReader.values.put(Constants.OS_USER_NAME_KEY, "os user name"); + localConfig.uncache(UserConfig.KEY); + authorName = localConfig.get(UserConfig.KEY).getAuthorName(); + assertEquals("os user name", authorName); + + if (hostname != null && hostname.length() != 0) { + authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail(); + assertEquals("os user name@" + hostname, authorEmail); + } + + // the git environment variables are defined + mockSystemReader.values.put(Constants.GIT_AUTHOR_NAME_KEY, "git author name"); + mockSystemReader.values.put(Constants.GIT_AUTHOR_EMAIL_KEY, "author@email"); + localConfig.uncache(UserConfig.KEY); + authorName = localConfig.get(UserConfig.KEY).getAuthorName(); + authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail(); + assertEquals("git author name", authorName); + assertEquals("author@email", authorEmail); + + // the values are defined in the global configuration + userGitConfig.setString("user", null, "name", "global username"); + userGitConfig.setString("user", null, "email", "author@globalemail"); + authorName = localConfig.get(UserConfig.KEY).getAuthorName(); + authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail(); + assertEquals("global username", authorName); + assertEquals("author@globalemail", authorEmail); + + // the values are defined in the local configuration + localConfig.setString("user", null, "name", "local username"); + localConfig.setString("user", null, "email", "author@localemail"); + authorName = localConfig.get(UserConfig.KEY).getAuthorName(); + authorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail(); + assertEquals("local username", authorName); + assertEquals("author@localemail", authorEmail); + + authorName = localConfig.get(UserConfig.KEY).getCommitterName(); + authorEmail = localConfig.get(UserConfig.KEY).getCommitterEmail(); + assertEquals("local username", authorName); + assertEquals("author@localemail", authorEmail); + } + + public void testReadBoolean_TrueFalse1() throws ConfigInvalidException { + final Config c = parse("[s]\na = true\nb = false\n"); + assertEquals("true", c.getString("s", null, "a")); + assertEquals("false", c.getString("s", null, "b")); + + assertTrue(c.getBoolean("s", "a", false)); + assertFalse(c.getBoolean("s", "b", true)); + } + + public void testReadBoolean_TrueFalse2() throws ConfigInvalidException { + final Config c = parse("[s]\na = TrUe\nb = fAlSe\n"); + assertEquals("TrUe", c.getString("s", null, "a")); + assertEquals("fAlSe", c.getString("s", null, "b")); + + assertTrue(c.getBoolean("s", "a", false)); + assertFalse(c.getBoolean("s", "b", true)); + } + + public void testReadBoolean_YesNo1() throws ConfigInvalidException { + final Config c = parse("[s]\na = yes\nb = no\n"); + assertEquals("yes", c.getString("s", null, "a")); + assertEquals("no", c.getString("s", null, "b")); + + assertTrue(c.getBoolean("s", "a", false)); + assertFalse(c.getBoolean("s", "b", true)); + } + + public void testReadBoolean_YesNo2() throws ConfigInvalidException { + final Config c = parse("[s]\na = yEs\nb = NO\n"); + assertEquals("yEs", c.getString("s", null, "a")); + assertEquals("NO", c.getString("s", null, "b")); + + assertTrue(c.getBoolean("s", "a", false)); + assertFalse(c.getBoolean("s", "b", true)); + } + + public void testReadBoolean_OnOff1() throws ConfigInvalidException { + final Config c = parse("[s]\na = on\nb = off\n"); + assertEquals("on", c.getString("s", null, "a")); + assertEquals("off", c.getString("s", null, "b")); + + assertTrue(c.getBoolean("s", "a", false)); + assertFalse(c.getBoolean("s", "b", true)); + } + + public void testReadBoolean_OnOff2() throws ConfigInvalidException { + final Config c = parse("[s]\na = ON\nb = OFF\n"); + assertEquals("ON", c.getString("s", null, "a")); + assertEquals("OFF", c.getString("s", null, "b")); + + assertTrue(c.getBoolean("s", "a", false)); + assertFalse(c.getBoolean("s", "b", true)); + } + + public void testReadLong() throws ConfigInvalidException { + assertReadLong(1L); + assertReadLong(-1L); + assertReadLong(Long.MIN_VALUE); + assertReadLong(Long.MAX_VALUE); + assertReadLong(4L * 1024 * 1024 * 1024, "4g"); + assertReadLong(3L * 1024 * 1024, "3 m"); + assertReadLong(8L * 1024, "8 k"); + + try { + assertReadLong(-1, "1.5g"); + fail("incorrectly accepted 1.5g"); + } catch (IllegalArgumentException e) { + assertEquals("Invalid integer value: s.a=1.5g", e.getMessage()); + } + } + + private void assertReadLong(long exp) throws ConfigInvalidException { + assertReadLong(exp, String.valueOf(exp)); + } + + private void assertReadLong(long exp, String act) + throws ConfigInvalidException { + final Config c = parse("[s]\na = " + act + "\n"); + assertEquals(exp, c.getLong("s", null, "a", 0L)); + } + + private Config parse(final String content) throws ConfigInvalidException { + final Config c = new Config(null); + c.fromText(content); + return c; + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java new file mode 100644 index 000000000..2870e4126 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryTestCase.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2007-2009, Robin Rosenberg + * Copyright (C) 2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +import org.eclipse.jgit.util.JGitTestUtil; +import org.eclipse.jgit.util.SystemReader; + +/** + * Base class for most JGit unit tests. + * + * Sets up a predefined test repository and has support for creating additional + * repositories and destroying them when the tests are finished. + * + * A system property jgit.junit.usemmap defines whether memory mapping + * is used. Memory mapping has an effect on the file system, in that memory + * mapped files in java cannot be deleted as long as they mapped arrays have not + * been reclaimed by the garbage collector. The programmer cannot control this + * with precision, though hinting using {@link java.lang.System#gc} + * often helps. + */ +public abstract class RepositoryTestCase extends TestCase { + + protected final File trashParent = new File("trash"); + + protected File trash; + + protected File trash_git; + + protected static final PersonIdent jauthor; + + protected static final PersonIdent jcommitter; + + static { + jauthor = new PersonIdent("J. Author", "jauthor@example.com"); + jcommitter = new PersonIdent("J. Committer", "jcommitter@example.com"); + } + + protected boolean packedGitMMAP; + + /** + * Configure JGit before setting up test repositories. + */ + protected void configure() { + final WindowCacheConfig c = new WindowCacheConfig(); + c.setPackedGitLimit(128 * WindowCacheConfig.KB); + c.setPackedGitWindowSize(8 * WindowCacheConfig.KB); + c.setPackedGitMMAP("true".equals(System.getProperty("jgit.junit.usemmap"))); + c.setDeltaBaseCacheLimit(8 * WindowCacheConfig.KB); + WindowCache.reconfigure(c); + } + + /** + * Utility method to delete a directory recursively. It is + * also used internally. If a file or directory cannot be removed + * it throws an AssertionFailure. + * + * @param dir + */ + protected void recursiveDelete(final File dir) { + recursiveDelete(dir, false, getClass().getName() + "." + getName(), true); + } + + protected static boolean recursiveDelete(final File dir, boolean silent, + final String name, boolean failOnError) { + assert !(silent && failOnError); + if (!dir.exists()) + return silent; + final File[] ls = dir.listFiles(); + if (ls != null) { + for (int k = 0; k < ls.length; k++) { + final File e = ls[k]; + if (e.isDirectory()) { + silent = recursiveDelete(e, silent, name, failOnError); + } else { + if (!e.delete()) { + if (!silent) { + reportDeleteFailure(name, failOnError, e); + } + silent = !failOnError; + } + } + } + } + if (!dir.delete()) { + if (!silent) { + reportDeleteFailure(name, failOnError, dir); + } + silent = !failOnError; + } + return silent; + } + + private static void reportDeleteFailure(final String name, + boolean failOnError, final File e) { + String severity; + if (failOnError) + severity = "Error"; + else + severity = "Warning"; + String msg = severity + ": Failed to delete " + e; + if (name != null) + msg += " in " + name; + if (failOnError) + fail(msg); + else + System.out.println(msg); + } + + protected static void copyFile(final File src, final File dst) + throws IOException { + final FileInputStream fis = new FileInputStream(src); + try { + final FileOutputStream fos = new FileOutputStream(dst); + try { + final byte[] buf = new byte[4096]; + int r; + while ((r = fis.read(buf)) > 0) { + fos.write(buf, 0, r); + } + } finally { + fos.close(); + } + } finally { + fis.close(); + } + } + + protected File writeTrashFile(final String name, final String data) + throws IOException { + File tf = new File(trash, name); + File tfp = tf.getParentFile(); + if (!tfp.exists() && !tf.getParentFile().mkdirs()) + throw new Error("Could not create directory " + tf.getParentFile()); + final OutputStreamWriter fw = new OutputStreamWriter( + new FileOutputStream(tf), "UTF-8"); + try { + fw.write(data); + } finally { + fw.close(); + } + return tf; + } + + protected static void checkFile(File f, final String checkData) + throws IOException { + Reader r = new InputStreamReader(new FileInputStream(f), "ISO-8859-1"); + try { + char[] data = new char[(int) f.length()]; + if (f.length() != r.read(data)) + throw new IOException("Internal error reading file data from "+f); + assertEquals(checkData, new String(data)); + } finally { + r.close(); + } + } + + protected Repository db; + + private static Thread shutdownhook; + private static List shutDownCleanups = new ArrayList(); + private static int testcount; + + private ArrayList repositoriesToClose = new ArrayList(); + + public void setUp() throws Exception { + super.setUp(); + configure(); + final String name = getClass().getName() + "." + getName(); + recursiveDelete(trashParent, true, name, false); // Cleanup old failed stuff + trash = new File(trashParent,"trash"+System.currentTimeMillis()+"."+(testcount++)); + trash_git = new File(trash, ".git").getCanonicalFile(); + if (shutdownhook == null) { + shutdownhook = new Thread() { + @Override + public void run() { + // This may look superfluous, but is an extra attempt + // to clean up. First GC to release as many resources + // as possible and then try to clean up one test repo + // at a time (to record problems) and finally to drop + // the directory containing all test repositories. + System.gc(); + for (Runnable r : shutDownCleanups) + r.run(); + recursiveDelete(trashParent, false, null, false); + } + }; + Runtime.getRuntime().addShutdownHook(shutdownhook); + } + + final MockSystemReader mockSystemReader = new MockSystemReader(); + mockSystemReader.userGitConfig = new FileBasedConfig(new File( + trash_git, "usergitconfig")); + SystemReader.setInstance(mockSystemReader); + + db = new Repository(trash_git); + db.create(); + + final String[] packs = { + "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f", + "pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371", + "pack-9fb5b411fe6dfa89cc2e6b89d2bd8e5de02b5745", + "pack-546ff360fe3488adb20860ce3436a2d6373d2796", + "pack-cbdeda40019ae0e6e789088ea0f51f164f489d14", + "pack-e6d07037cbcf13376308a0a995d1fa48f8f76aaa", + "pack-3280af9c07ee18a87705ef50b0cc4cd20266cf12" + }; + final File packDir = new File(db.getObjectsDirectory(), "pack"); + for (int k = 0; k < packs.length; k++) { + copyFile(JGitTestUtil.getTestResourceFile(packs[k] + ".pack"), new File(packDir, + packs[k] + ".pack")); + copyFile(JGitTestUtil.getTestResourceFile(packs[k] + ".idx"), new File(packDir, + packs[k] + ".idx")); + } + + copyFile(JGitTestUtil.getTestResourceFile("packed-refs"), new File(trash_git,"packed-refs")); + } + + protected void tearDown() throws Exception { + RepositoryCache.clear(); + db.close(); + for (Repository r : repositoriesToClose) + r.close(); + + // Since memory mapping is controlled by the GC we need to + // tell it this is a good time to clean up and unlock + // memory mapped files. + if (packedGitMMAP) + System.gc(); + + final String name = getClass().getName() + "." + getName(); + recursiveDelete(trash, false, name, true); + for (Repository r : repositoriesToClose) + recursiveDelete(r.getWorkDir(), false, name, true); + repositoriesToClose.clear(); + + super.tearDown(); + } + + /** + * Helper for creating extra empty repos + * + * @return a new empty git repository for testing purposes + * + * @throws IOException + */ + protected Repository createNewEmptyRepo() throws IOException { + return createNewEmptyRepo(false); + } + + /** + * Helper for creating extra empty repos + * + * @param bare if true, create a bare repository. + * @return a new empty git repository for testing purposes + * + * @throws IOException + */ + protected Repository createNewEmptyRepo(boolean bare) throws IOException { + final File newTestRepo = new File(trashParent, "new" + + System.currentTimeMillis() + "." + (testcount++) + + (bare ? "" : "/") + ".git").getCanonicalFile(); + assertFalse(newTestRepo.exists()); + final Repository newRepo = new Repository(newTestRepo); + newRepo.create(); + final String name = getClass().getName() + "." + getName(); + shutDownCleanups.add(new Runnable() { + public void run() { + recursiveDelete(newTestRepo, false, name, false); + } + }); + repositoriesToClose.add(newRepo); + return newRepo; + } + + protected void setupReflog(String logName, byte[] data) + throws FileNotFoundException, IOException { + File logfile = new File(db.getDirectory(), logName); + if (!logfile.getParentFile().mkdirs() + && !logfile.getParentFile().isDirectory()) { + throw new IOException( + "oops, cannot create the directory for the test reflog file" + + logfile); + } + FileOutputStream fileOutputStream = new FileOutputStream(logfile); + try { + fileOutputStream.write(data); + } finally { + fileOutputStream.close(); + } + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_ObjectId.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_ObjectId.java new file mode 100644 index 000000000..03176cb8f --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_ObjectId.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2008, Jonas Fonseca + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 junit.framework.TestCase; + +public class T0001_ObjectId extends TestCase { + public void test001_toString() { + final String x = "def4c620bc3713bb1bb26b808ec9312548e73946"; + final ObjectId oid = ObjectId.fromString(x); + assertEquals(x, oid.name()); + } + + public void test002_toString() { + final String x = "ff00eedd003713bb1bb26b808ec9312548e73946"; + final ObjectId oid = ObjectId.fromString(x); + assertEquals(x, oid.name()); + } + + public void test003_equals() { + final String x = "def4c620bc3713bb1bb26b808ec9312548e73946"; + final ObjectId a = ObjectId.fromString(x); + final ObjectId b = ObjectId.fromString(x); + assertEquals(a.hashCode(), b.hashCode()); + assertTrue("a and b are same", a.equals(b)); + } + + public void test004_isId() { + assertTrue("valid id", ObjectId + .isId("def4c620bc3713bb1bb26b808ec9312548e73946")); + } + + public void test005_notIsId() { + assertFalse("bob is not an id", ObjectId.isId("bob")); + } + + public void test006_notIsId() { + assertFalse("39 digits is not an id", ObjectId + .isId("def4c620bc3713bb1bb26b808ec9312548e7394")); + } + + public void test007_isId() { + assertTrue("uppercase is accepted", ObjectId + .isId("Def4c620bc3713bb1bb26b808ec9312548e73946")); + } + + public void test008_notIsId() { + assertFalse("g is not a valid hex digit", ObjectId + .isId("gef4c620bc3713bb1bb26b808ec9312548e73946")); + } + + public void test009_toString() { + final String x = "ff00eedd003713bb1bb26b808ec9312548e73946"; + final ObjectId oid = ObjectId.fromString(x); + assertEquals(x, ObjectId.toString(oid)); + } + + public void test010_toString() { + final String x = "0000000000000000000000000000000000000000"; + assertEquals(x, ObjectId.toString(null)); + } + + public void test011_toString() { + final String x = "0123456789ABCDEFabcdef1234567890abcdefAB"; + final ObjectId oid = ObjectId.fromString(x); + assertEquals(x.toLowerCase(), oid.name()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdent.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdent.java new file mode 100644 index 000000000..aaa88c028 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0001_PersonIdent.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.Date; +import java.util.TimeZone; + +import junit.framework.TestCase; + +public class T0001_PersonIdent extends TestCase { + public void test001_NewIdent() { + final PersonIdent p = new PersonIdent("A U Thor", "author@example.com", + new Date(1142878501000L), TimeZone.getTimeZone("EST")); + assertEquals("A U Thor", p.getName()); + assertEquals("author@example.com", p.getEmailAddress()); + assertEquals(1142878501000L, p.getWhen().getTime()); + assertEquals("A U Thor 1142878501 -0500", p + .toExternalString()); + } + + public void test002_ParseIdent() { + final String i = "A U Thor 1142878501 -0500"; + final PersonIdent p = new PersonIdent(i); + assertEquals(i, p.toExternalString()); + assertEquals("A U Thor", p.getName()); + assertEquals("author@example.com", p.getEmailAddress()); + assertEquals(1142878501000L, p.getWhen().getTime()); + } + + public void test003_ParseIdent() { + final String i = "A U Thor 1142878501 +0230"; + final PersonIdent p = new PersonIdent(i); + assertEquals(i, p.toExternalString()); + assertEquals("A U Thor", p.getName()); + assertEquals("author@example.com", p.getEmailAddress()); + assertEquals(1142878501000L, p.getWhen().getTime()); + } + + public void test004_ParseIdent() { + final String i = "A U Thor 1142878501 +0230"; + final PersonIdent p = new PersonIdent(i); + assertEquals("A U Thor", p.getName()); + assertEquals("author@example.com", p.getEmailAddress()); + assertEquals(1142878501000L, p.getWhen().getTime()); + } + + public void test005_ParseIdent() { + final String i = "A U Thor1142878501 +0230"; + final PersonIdent p = new PersonIdent(i); + assertEquals("A U Thor", p.getName()); + assertEquals("author@example.com", p.getEmailAddress()); + assertEquals(1142878501000L, p.getWhen().getTime()); + } + + public void test006_ParseIdent() { + final String i = "A U Thor 1142878501 +0230"; + final PersonIdent p = new PersonIdent(i); + assertEquals("A U Thor", p.getName()); + assertEquals("author@example.com", p.getEmailAddress()); + assertEquals(1142878501000L, p.getWhen().getTime()); + } + + public void test007_ParseIdent() { + final String i = "A U Thor1142878501 +0230 "; + final PersonIdent p = new PersonIdent(i); + assertEquals("A U Thor", p.getName()); + assertEquals("author@example.com", p.getEmailAddress()); + assertEquals(1142878501000L, p.getWhen().getTime()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_Tree.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_Tree.java new file mode 100644 index 000000000..66c3c75f1 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_Tree.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +public class T0002_Tree extends RepositoryTestCase { + private static final ObjectId SOME_FAKE_ID = ObjectId.fromString( + "0123456789abcdef0123456789abcdef01234567"); + + private int compareNamesUsingSpecialCompare(String a,String b) throws UnsupportedEncodingException { + char lasta = '\0'; + byte[] abytes; + if (a.length() > 0 && a.charAt(a.length()-1) == '/') { + lasta = '/'; + a = a.substring(0, a.length() - 1); + } + abytes = a.getBytes("ISO-8859-1"); + char lastb = '\0'; + byte[] bbytes; + if (b.length() > 0 && b.charAt(b.length()-1) == '/') { + lastb = '/'; + b = b.substring(0, b.length() - 1); + } + bbytes = b.getBytes("ISO-8859-1"); + return Tree.compareNames(abytes, bbytes, lasta, lastb); + } + + public void test000_sort_01() throws UnsupportedEncodingException { + assertEquals(0, compareNamesUsingSpecialCompare("a","a")); + } + public void test000_sort_02() throws UnsupportedEncodingException { + assertEquals(-1, compareNamesUsingSpecialCompare("a","b")); + assertEquals(1, compareNamesUsingSpecialCompare("b","a")); + } + public void test000_sort_03() throws UnsupportedEncodingException { + assertEquals(1, compareNamesUsingSpecialCompare("a:","a")); + assertEquals(1, compareNamesUsingSpecialCompare("a/","a")); + assertEquals(-1, compareNamesUsingSpecialCompare("a","a/")); + assertEquals(-1, compareNamesUsingSpecialCompare("a","a:")); + assertEquals(1, compareNamesUsingSpecialCompare("a:","a/")); + assertEquals(-1, compareNamesUsingSpecialCompare("a/","a:")); + } + public void test000_sort_04() throws UnsupportedEncodingException { + assertEquals(-1, compareNamesUsingSpecialCompare("a.a","a/a")); + assertEquals(1, compareNamesUsingSpecialCompare("a/a","a.a")); + } + public void test000_sort_05() throws UnsupportedEncodingException { + assertEquals(-1, compareNamesUsingSpecialCompare("a.","a/")); + assertEquals(1, compareNamesUsingSpecialCompare("a/","a.")); + + } + + public void test001_createEmpty() throws IOException { + final Tree t = new Tree(db); + assertTrue("isLoaded", t.isLoaded()); + assertTrue("isModified", t.isModified()); + assertTrue("no parent", t.getParent() == null); + assertTrue("isRoot", t.isRoot()); + assertTrue("no name", t.getName() == null); + assertTrue("no nameUTF8", t.getNameUTF8() == null); + assertTrue("has entries array", t.members() != null); + assertTrue("entries is empty", t.members().length == 0); + assertEquals("full name is empty", "", t.getFullName()); + assertTrue("no id", t.getId() == null); + assertTrue("tree is self", t.getTree() == t); + assertTrue("database is r", t.getRepository() == db); + assertTrue("no foo child", t.findTreeMember("foo") == null); + assertTrue("no foo child", t.findBlobMember("foo") == null); + } + + public void test002_addFile() throws IOException { + final Tree t = new Tree(db); + t.setId(SOME_FAKE_ID); + assertTrue("has id", t.getId() != null); + assertFalse("not modified", t.isModified()); + + final String n = "bob"; + final FileTreeEntry f = t.addFile(n); + assertNotNull("have file", f); + assertEquals("name matches", n, f.getName()); + assertEquals("name matches", f.getName(), new String(f.getNameUTF8(), + "UTF-8")); + assertEquals("full name matches", n, f.getFullName()); + assertTrue("no id", f.getId() == null); + assertTrue("is modified", t.isModified()); + assertTrue("has no id", t.getId() == null); + assertTrue("found bob", t.findBlobMember(f.getName()) == f); + + final TreeEntry[] i = t.members(); + assertNotNull("members array not null", i); + assertTrue("iterator is not empty", i != null && i.length > 0); + assertTrue("iterator returns file", i != null && i[0] == f); + assertTrue("iterator is empty", i != null && i.length == 1); + } + + public void test004_addTree() throws IOException { + final Tree t = new Tree(db); + t.setId(SOME_FAKE_ID); + assertTrue("has id", t.getId() != null); + assertFalse("not modified", t.isModified()); + + final String n = "bob"; + final Tree f = t.addTree(n); + assertNotNull("have tree", f); + assertEquals("name matches", n, f.getName()); + assertEquals("name matches", f.getName(), new String(f.getNameUTF8(), + "UTF-8")); + assertEquals("full name matches", n, f.getFullName()); + assertTrue("no id", f.getId() == null); + assertTrue("parent matches", f.getParent() == t); + assertTrue("repository matches", f.getRepository() == db); + assertTrue("isLoaded", f.isLoaded()); + assertFalse("has items", f.members().length > 0); + assertFalse("is root", f.isRoot()); + assertTrue("tree is self", f.getTree() == f); + assertTrue("parent is modified", t.isModified()); + assertTrue("parent has no id", t.getId() == null); + assertTrue("found bob child", t.findTreeMember(f.getName()) == f); + + final TreeEntry[] i = t.members(); + assertTrue("iterator is not empty", i.length > 0); + assertTrue("iterator returns file", i[0] == f); + assertTrue("iterator is empty", i.length == 1); + } + + public void test005_addRecursiveFile() throws IOException { + final Tree t = new Tree(db); + final FileTreeEntry f = t.addFile("a/b/c"); + assertNotNull("created f", f); + assertEquals("c", f.getName()); + assertEquals("b", f.getParent().getName()); + assertEquals("a", f.getParent().getParent().getName()); + assertTrue("t is great-grandparent", t == f.getParent().getParent() + .getParent()); + } + + public void test005_addRecursiveTree() throws IOException { + final Tree t = new Tree(db); + final Tree f = t.addTree("a/b/c"); + assertNotNull("created f", f); + assertEquals("c", f.getName()); + assertEquals("b", f.getParent().getName()); + assertEquals("a", f.getParent().getParent().getName()); + assertTrue("t is great-grandparent", t == f.getParent().getParent() + .getParent()); + } + + public void test006_addDeepTree() throws IOException { + final Tree t = new Tree(db); + + final Tree e = t.addTree("e"); + assertNotNull("have e", e); + assertTrue("e.parent == t", e.getParent() == t); + final Tree f = t.addTree("f"); + assertNotNull("have f", f); + assertTrue("f.parent == t", f.getParent() == t); + final Tree g = f.addTree("g"); + assertNotNull("have g", g); + assertTrue("g.parent == f", g.getParent() == f); + final Tree h = g.addTree("h"); + assertNotNull("have h", h); + assertTrue("h.parent = g", h.getParent() == g); + + h.setId(SOME_FAKE_ID); + assertTrue("h not modified", !h.isModified()); + g.setId(SOME_FAKE_ID); + assertTrue("g not modified", !g.isModified()); + f.setId(SOME_FAKE_ID); + assertTrue("f not modified", !f.isModified()); + e.setId(SOME_FAKE_ID); + assertTrue("e not modified", !e.isModified()); + t.setId(SOME_FAKE_ID); + assertTrue("t not modified.", !t.isModified()); + + assertEquals("full path of h ok", "f/g/h", h.getFullName()); + assertTrue("Can find h", t.findTreeMember(h.getFullName()) == h); + assertTrue("Can't find f/z", t.findBlobMember("f/z") == null); + assertTrue("Can't find y/z", t.findBlobMember("y/z") == null); + + final FileTreeEntry i = h.addFile("i"); + assertNotNull(i); + assertEquals("full path of i ok", "f/g/h/i", i.getFullName()); + assertTrue("Can find i", t.findBlobMember(i.getFullName()) == i); + assertTrue("h modified", h.isModified()); + assertTrue("g modified", g.isModified()); + assertTrue("f modified", f.isModified()); + assertTrue("e not modified", !e.isModified()); + assertTrue("t modified", t.isModified()); + + assertTrue("h no id", h.getId() == null); + assertTrue("g no id", g.getId() == null); + assertTrue("f no id", f.getId() == null); + assertTrue("e has id", e.getId() != null); + assertTrue("t no id", t.getId() == null); + } + + public void test007_manyFileLookup() throws IOException { + final Tree t = new Tree(db); + final List files = new ArrayList(26 * 26); + for (char level1 = 'a'; level1 <= 'z'; level1++) { + for (char level2 = 'a'; level2 <= 'z'; level2++) { + final String n = "." + level1 + level2 + "9"; + final FileTreeEntry f = t.addFile(n); + assertNotNull("File " + n + " added.", f); + assertEquals(n, f.getName()); + files.add(f); + } + } + assertEquals(files.size(), t.memberCount()); + final TreeEntry[] ents = t.members(); + assertNotNull(ents); + assertEquals(files.size(), ents.length); + for (int k = 0; k < ents.length; k++) { + assertTrue("File " + files.get(k).getName() + + " is at " + k + ".", files.get(k) == ents[k]); + } + } + + public void test008_SubtreeInternalSorting() throws IOException { + final Tree t = new Tree(db); + final FileTreeEntry e0 = t.addFile("a-b"); + final FileTreeEntry e1 = t.addFile("a-"); + final FileTreeEntry e2 = t.addFile("a=b"); + final Tree e3 = t.addTree("a"); + final FileTreeEntry e4 = t.addFile("a="); + + final TreeEntry[] ents = t.members(); + assertSame(e1, ents[0]); + assertSame(e0, ents[1]); + assertSame(e3, ents[2]); + assertSame(e4, ents[3]); + assertSame(e2, ents[4]); + } + + public void test009_SymlinkAndGitlink() throws IOException { + final Tree symlinkTree = db.mapTree("symlink"); + assertTrue("Symlink entry exists", symlinkTree.existsBlob("symlink.txt")); + final Tree gitlinkTree = db.mapTree("gitlink"); + assertTrue("Gitlink entry exists", gitlinkTree.existsBlob("submodule")); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java new file mode 100644 index 000000000..d17cea6ae --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0003_Basic.java @@ -0,0 +1,581 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2007-2008, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; + +import org.eclipse.jgit.errors.ConfigInvalidException; + +public class T0003_Basic extends RepositoryTestCase { + public void test001_Initalize() { + final File gitdir = new File(trash, ".git"); + final File objects = new File(gitdir, "objects"); + final File objects_pack = new File(objects, "pack"); + final File objects_info = new File(objects, "info"); + final File refs = new File(gitdir, "refs"); + final File refs_heads = new File(refs, "heads"); + final File refs_tags = new File(refs, "tags"); + final File HEAD = new File(gitdir, "HEAD"); + + assertTrue("Exists " + trash, trash.isDirectory()); + assertTrue("Exists " + objects, objects.isDirectory()); + assertTrue("Exists " + objects_pack, objects_pack.isDirectory()); + assertTrue("Exists " + objects_info, objects_info.isDirectory()); + assertEquals(2, objects.listFiles().length); + assertTrue("Exists " + refs, refs.isDirectory()); + assertTrue("Exists " + refs_heads, refs_heads.isDirectory()); + assertTrue("Exists " + refs_tags, refs_tags.isDirectory()); + assertTrue("Exists " + HEAD, HEAD.isFile()); + assertEquals(23, HEAD.length()); + } + + public void test002_WriteEmptyTree() throws IOException { + // One of our test packs contains the empty tree object. If the pack is + // open when we create it we won't write the object file out as a loose + // object (as it already exists in the pack). + // + final Repository newdb = createNewEmptyRepo(); + final Tree t = new Tree(newdb); + t.accept(new WriteTree(trash, newdb), TreeEntry.MODIFIED_ONLY); + assertEquals("4b825dc642cb6eb9a060e54bf8d69288fbee4904", t.getId() + .name()); + final File o = new File(new File(new File(newdb.getDirectory(), + "objects"), "4b"), "825dc642cb6eb9a060e54bf8d69288fbee4904"); + assertTrue("Exists " + o, o.isFile()); + assertTrue("Read-only " + o, !o.canWrite()); + } + + public void test002_WriteEmptyTree2() throws IOException { + // File shouldn't exist as it is in a test pack. + // + final Tree t = new Tree(db); + t.accept(new WriteTree(trash, db), TreeEntry.MODIFIED_ONLY); + assertEquals("4b825dc642cb6eb9a060e54bf8d69288fbee4904", t.getId() + .name()); + final File o = new File(new File(new File(trash_git, "objects"), "4b"), + "825dc642cb6eb9a060e54bf8d69288fbee4904"); + assertFalse("Exists " + o, o.isFile()); + } + + public void test003_WriteShouldBeEmptyTree() throws IOException { + final Tree t = new Tree(db); + final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); + t.addFile("should-be-empty").setId(emptyId); + t.accept(new WriteTree(trash, db), TreeEntry.MODIFIED_ONLY); + assertEquals("7bb943559a305bdd6bdee2cef6e5df2413c3d30a", t.getId() + .name()); + + File o; + o = new File(new File(new File(trash_git, "objects"), "7b"), + "b943559a305bdd6bdee2cef6e5df2413c3d30a"); + assertTrue("Exists " + o, o.isFile()); + assertTrue("Read-only " + o, !o.canWrite()); + + o = new File(new File(new File(trash_git, "objects"), "e6"), + "9de29bb2d1d6434b8b29ae775ad8c2e48c5391"); + assertTrue("Exists " + o, o.isFile()); + assertTrue("Read-only " + o, !o.canWrite()); + } + + public void test004_CheckNewConfig() { + final RepositoryConfig c = db.getConfig(); + assertNotNull(c); + assertEquals("0", c.getString("core", null, "repositoryformatversion")); + assertEquals("0", c.getString("CoRe", null, "REPOSITORYFoRmAtVeRsIoN")); + assertEquals("true", c.getString("core", null, "filemode")); + assertEquals("true", c.getString("cOrE", null, "fIlEModE")); + assertNull(c.getString("notavalue", null, "reallyNotAValue")); + } + + public void test005_ReadSimpleConfig() { + final RepositoryConfig c = db.getConfig(); + assertNotNull(c); + assertEquals("0", c.getString("core", null, "repositoryformatversion")); + assertEquals("0", c.getString("CoRe", null, "REPOSITORYFoRmAtVeRsIoN")); + assertEquals("true", c.getString("core", null, "filemode")); + assertEquals("true", c.getString("cOrE", null, "fIlEModE")); + assertNull(c.getString("notavalue", null, "reallyNotAValue")); + } + + public void test006_ReadUglyConfig() throws IOException, + ConfigInvalidException { + final RepositoryConfig c = db.getConfig(); + final File cfg = new File(db.getDirectory(), "config"); + final FileWriter pw = new FileWriter(cfg); + final String configStr = " [core];comment\n\tfilemode = yes\n" + + "[user]\n" + + " email = A U Thor # Just an example...\n" + + " name = \"A Thor \\\\ \\\"\\t \"\n" + + " defaultCheckInComment = a many line\\n\\\ncomment\\n\\\n" + + " to test\n"; + pw.write(configStr); + pw.close(); + c.load(); + assertEquals("yes", c.getString("core", null, "filemode")); + assertEquals("A U Thor ", c + .getString("user", null, "email")); + assertEquals("A Thor \\ \"\t ", c.getString("user", null, "name")); + assertEquals("a many line\ncomment\n to test", c.getString("user", + null, "defaultCheckInComment")); + c.save(); + final FileReader fr = new FileReader(cfg); + final char[] cbuf = new char[configStr.length()]; + fr.read(cbuf); + fr.close(); + assertEquals(configStr, new String(cbuf)); + } + + public void test007_Open() throws IOException { + final Repository db2 = new Repository(db.getDirectory()); + assertEquals(db.getDirectory(), db2.getDirectory()); + assertEquals(db.getObjectsDirectory(), db2.getObjectsDirectory()); + assertNotSame(db.getConfig(), db2.getConfig()); + } + + public void test008_FailOnWrongVersion() throws IOException { + final File cfg = new File(db.getDirectory(), "config"); + final FileWriter pw = new FileWriter(cfg); + final String badvers = "ihopethisisneveraversion"; + final String configStr = "[core]\n" + "\trepositoryFormatVersion=" + + badvers + "\n"; + pw.write(configStr); + pw.close(); + + try { + new Repository(db.getDirectory()); + fail("incorrectly opened a bad repository"); + } catch (IOException ioe) { + assertTrue(ioe.getMessage().indexOf("format") > 0); + assertTrue(ioe.getMessage().indexOf(badvers) > 0); + } + } + + public void test009_CreateCommitOldFormat() throws IOException, + ConfigInvalidException { + writeTrashFile(".git/config", "[core]\n" + "legacyHeaders=1\n"); + db.getConfig().load(); + + final Tree t = new Tree(db); + final FileTreeEntry f = t.addFile("i-am-a-file"); + writeTrashFile(f.getName(), "and this is the data in me\n"); + t.accept(new WriteTree(trash, db), TreeEntry.MODIFIED_ONLY); + assertEquals(ObjectId.fromString("00b1f73724f493096d1ffa0b0f1f1482dbb8c936"), + t.getTreeId()); + + final Commit c = new Commit(db); + c.setAuthor(new PersonIdent(jauthor, 1154236443000L, -4 * 60)); + c.setCommitter(new PersonIdent(jcommitter, 1154236443000L, -4 * 60)); + c.setMessage("A Commit\n"); + c.setTree(t); + assertEquals(t.getTreeId(), c.getTreeId()); + c.commit(); + final ObjectId cmtid = ObjectId.fromString( + "803aec4aba175e8ab1d666873c984c0308179099"); + assertEquals(cmtid, c.getCommitId()); + + // Verify the commit we just wrote is in the correct format. + final XInputStream xis = new XInputStream(new FileInputStream(db + .toFile(cmtid))); + try { + assertEquals(0x78, xis.readUInt8()); + assertEquals(0x9c, xis.readUInt8()); + assertTrue(0x789c % 31 == 0); + } finally { + xis.close(); + } + + // Verify we can read it. + final Commit c2 = db.mapCommit(cmtid); + assertNotNull(c2); + assertEquals(c.getMessage(), c2.getMessage()); + assertEquals(c.getTreeId(), c2.getTreeId()); + assertEquals(c.getAuthor(), c2.getAuthor()); + assertEquals(c.getCommitter(), c2.getCommitter()); + } + + public void test012_SubtreeExternalSorting() throws IOException { + final ObjectId emptyBlob = new ObjectWriter(db).writeBlob(new byte[0]); + final Tree t = new Tree(db); + final FileTreeEntry e0 = t.addFile("a-"); + final FileTreeEntry e1 = t.addFile("a-b"); + final FileTreeEntry e2 = t.addFile("a/b"); + final FileTreeEntry e3 = t.addFile("a="); + final FileTreeEntry e4 = t.addFile("a=b"); + + e0.setId(emptyBlob); + e1.setId(emptyBlob); + e2.setId(emptyBlob); + e3.setId(emptyBlob); + e4.setId(emptyBlob); + + t.accept(new WriteTree(trash, db), TreeEntry.MODIFIED_ONLY); + assertEquals(ObjectId.fromString("b47a8f0a4190f7572e11212769090523e23eb1ea"), + t.getId()); + } + + public void test020_createBlobTag() throws IOException { + final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); + final Tag t = new Tag(db); + t.setObjId(emptyId); + t.setType("blob"); + t.setTag("test020"); + t.setAuthor(new PersonIdent(jauthor, 1154236443000L, -4 * 60)); + t.setMessage("test020 tagged\n"); + t.tag(); + assertEquals("6759556b09fbb4fd8ae5e315134481cc25d46954", t.getTagId().name()); + + Tag mapTag = db.mapTag("test020"); + assertEquals("blob", mapTag.getType()); + assertEquals("test020 tagged\n", mapTag.getMessage()); + assertEquals(new PersonIdent(jauthor, 1154236443000L, -4 * 60), mapTag.getAuthor()); + assertEquals("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", mapTag.getObjId().name()); + } + + public void test020b_createBlobPlainTag() throws IOException { + test020_createBlobTag(); + Tag t = new Tag(db); + t.setTag("test020b"); + t.setObjId(ObjectId.fromString("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391")); + t.tag(); + + Tag mapTag = db.mapTag("test020b"); + assertEquals("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", mapTag.getObjId().name()); + + // We do not repeat the plain tag test for other object types + } + + public void test021_createTreeTag() throws IOException { + final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); + final Tree almostEmptyTree = new Tree(db); + almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, "empty".getBytes(), false)); + final ObjectId almostEmptyTreeId = new ObjectWriter(db).writeTree(almostEmptyTree); + final Tag t = new Tag(db); + t.setObjId(almostEmptyTreeId); + t.setType("tree"); + t.setTag("test021"); + t.setAuthor(new PersonIdent(jauthor, 1154236443000L, -4 * 60)); + t.setMessage("test021 tagged\n"); + t.tag(); + assertEquals("b0517bc8dbe2096b419d42424cd7030733f4abe5", t.getTagId().name()); + + Tag mapTag = db.mapTag("test021"); + assertEquals("tree", mapTag.getType()); + assertEquals("test021 tagged\n", mapTag.getMessage()); + assertEquals(new PersonIdent(jauthor, 1154236443000L, -4 * 60), mapTag.getAuthor()); + assertEquals("417c01c8795a35b8e835113a85a5c0c1c77f67fb", mapTag.getObjId().name()); + } + + public void test022_createCommitTag() throws IOException { + final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); + final Tree almostEmptyTree = new Tree(db); + almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, "empty".getBytes(), false)); + final ObjectId almostEmptyTreeId = new ObjectWriter(db).writeTree(almostEmptyTree); + final Commit almostEmptyCommit = new Commit(db); + almostEmptyCommit.setAuthor(new PersonIdent(jauthor, 1154236443000L, -2 * 60)); // not exactly the same + almostEmptyCommit.setCommitter(new PersonIdent(jauthor, 1154236443000L, -2 * 60)); + almostEmptyCommit.setMessage("test022\n"); + almostEmptyCommit.setTreeId(almostEmptyTreeId); + ObjectId almostEmptyCommitId = new ObjectWriter(db).writeCommit(almostEmptyCommit); + final Tag t = new Tag(db); + t.setObjId(almostEmptyCommitId); + t.setType("commit"); + t.setTag("test022"); + t.setAuthor(new PersonIdent(jauthor, 1154236443000L, -4 * 60)); + t.setMessage("test022 tagged\n"); + t.tag(); + assertEquals("0ce2ebdb36076ef0b38adbe077a07d43b43e3807", t.getTagId().name()); + + Tag mapTag = db.mapTag("test022"); + assertEquals("commit", mapTag.getType()); + assertEquals("test022 tagged\n", mapTag.getMessage()); + assertEquals(new PersonIdent(jauthor, 1154236443000L, -4 * 60), mapTag.getAuthor()); + assertEquals("b5d3b45a96b340441f5abb9080411705c51cc86c", mapTag.getObjId().name()); + } + + public void test023_createCommitNonAnullii() throws IOException { + final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); + final Tree almostEmptyTree = new Tree(db); + almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, "empty".getBytes(), false)); + final ObjectId almostEmptyTreeId = new ObjectWriter(db).writeTree(almostEmptyTree); + Commit commit = new Commit(db); + commit.setTreeId(almostEmptyTreeId); + commit.setAuthor(new PersonIdent("Joe H\u00e4cker","joe@example.com",4294967295000L,60)); + commit.setCommitter(new PersonIdent("Joe Hacker","joe2@example.com",4294967295000L,60)); + commit.setEncoding("UTF-8"); + commit.setMessage("\u00dcbergeeks"); + ObjectId cid = new ObjectWriter(db).writeCommit(commit); + assertEquals("4680908112778718f37e686cbebcc912730b3154", cid.name()); + } + + public void test024_createCommitNonAscii() throws IOException { + final ObjectId emptyId = new ObjectWriter(db).writeBlob(new byte[0]); + final Tree almostEmptyTree = new Tree(db); + almostEmptyTree.addEntry(new FileTreeEntry(almostEmptyTree, emptyId, "empty".getBytes(), false)); + final ObjectId almostEmptyTreeId = new ObjectWriter(db).writeTree(almostEmptyTree); + Commit commit = new Commit(db); + commit.setTreeId(almostEmptyTreeId); + commit.setAuthor(new PersonIdent("Joe H\u00e4cker","joe@example.com",4294967295000L,60)); + commit.setCommitter(new PersonIdent("Joe Hacker","joe2@example.com",4294967295000L,60)); + commit.setEncoding("ISO-8859-1"); + commit.setMessage("\u00dcbergeeks"); + ObjectId cid = new ObjectWriter(db).writeCommit(commit); + assertEquals("2979b39d385014b33287054b87f77bcb3ecb5ebf", cid.name()); + } + + public void test025_packedRefs() throws IOException { + test020_createBlobTag(); + test021_createTreeTag(); + test022_createCommitTag(); + + if (!new File(db.getDirectory(),"refs/tags/test020").delete()) throw new Error("Cannot delete unpacked tag"); + if (!new File(db.getDirectory(),"refs/tags/test021").delete()) throw new Error("Cannot delete unpacked tag"); + if (!new File(db.getDirectory(),"refs/tags/test022").delete()) throw new Error("Cannot delete unpacked tag"); + + // We cannot resolve it now, since we have no ref + Tag mapTag20missing = db.mapTag("test020"); + assertNull(mapTag20missing); + + // Construct packed refs file + PrintWriter w = new PrintWriter(new FileWriter(new File(db.getDirectory(), "packed-refs"))); + w.println("# packed-refs with: peeled"); + w.println("6759556b09fbb4fd8ae5e315134481cc25d46954 refs/tags/test020"); + w.println("^e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"); + w.println("b0517bc8dbe2096b419d42424cd7030733f4abe5 refs/tags/test021"); + w.println("^417c01c8795a35b8e835113a85a5c0c1c77f67fb"); + w.println("0ce2ebdb36076ef0b38adbe077a07d43b43e3807 refs/tags/test022"); + w.println("^b5d3b45a96b340441f5abb9080411705c51cc86c"); + w.close(); + + Tag mapTag20 = db.mapTag("test020"); + assertNotNull("have tag test020", mapTag20); + assertEquals("blob", mapTag20.getType()); + assertEquals("test020 tagged\n", mapTag20.getMessage()); + assertEquals(new PersonIdent(jauthor, 1154236443000L, -4 * 60), mapTag20.getAuthor()); + assertEquals("e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", mapTag20.getObjId().name()); + + Tag mapTag21 = db.mapTag("test021"); + assertEquals("tree", mapTag21.getType()); + assertEquals("test021 tagged\n", mapTag21.getMessage()); + assertEquals(new PersonIdent(jauthor, 1154236443000L, -4 * 60), mapTag21.getAuthor()); + assertEquals("417c01c8795a35b8e835113a85a5c0c1c77f67fb", mapTag21.getObjId().name()); + + Tag mapTag22 = db.mapTag("test022"); + assertEquals("commit", mapTag22.getType()); + assertEquals("test022 tagged\n", mapTag22.getMessage()); + assertEquals(new PersonIdent(jauthor, 1154236443000L, -4 * 60), mapTag22.getAuthor()); + assertEquals("b5d3b45a96b340441f5abb9080411705c51cc86c", mapTag22.getObjId().name()); + } + + public void test025_computeSha1NoStore() throws IOException { + byte[] data = "test025 some data, more than 16 bytes to get good coverage" + .getBytes("ISO-8859-1"); + // TODO: but we do not test legacy header writing + final ObjectId id = new ObjectWriter(db).computeBlobSha1(data.length, + new ByteArrayInputStream(data)); + assertEquals("4f561df5ecf0dfbd53a0dc0f37262fef075d9dde", id.name()); + } + + public void test026_CreateCommitMultipleparents() throws IOException { + final Tree t = new Tree(db); + final FileTreeEntry f = t.addFile("i-am-a-file"); + writeTrashFile(f.getName(), "and this is the data in me\n"); + t.accept(new WriteTree(trash, db), TreeEntry.MODIFIED_ONLY); + assertEquals(ObjectId.fromString("00b1f73724f493096d1ffa0b0f1f1482dbb8c936"), + t.getTreeId()); + + final Commit c1 = new Commit(db); + c1.setAuthor(new PersonIdent(jauthor, 1154236443000L, -4 * 60)); + c1.setCommitter(new PersonIdent(jcommitter, 1154236443000L, -4 * 60)); + c1.setMessage("A Commit\n"); + c1.setTree(t); + assertEquals(t.getTreeId(), c1.getTreeId()); + c1.commit(); + final ObjectId cmtid1 = ObjectId.fromString( + "803aec4aba175e8ab1d666873c984c0308179099"); + assertEquals(cmtid1, c1.getCommitId()); + + final Commit c2 = new Commit(db); + c2.setAuthor(new PersonIdent(jauthor, 1154236443000L, -4 * 60)); + c2.setCommitter(new PersonIdent(jcommitter, 1154236443000L, -4 * 60)); + c2.setMessage("A Commit 2\n"); + c2.setTree(t); + assertEquals(t.getTreeId(), c2.getTreeId()); + c2.setParentIds(new ObjectId[] { c1.getCommitId() } ); + c2.commit(); + final ObjectId cmtid2 = ObjectId.fromString( + "95d068687c91c5c044fb8c77c5154d5247901553"); + assertEquals(cmtid2, c2.getCommitId()); + + Commit rm2 = db.mapCommit(cmtid2); + assertNotSame(c2, rm2); // assert the parsed objects is not from the cache + assertEquals(c2.getAuthor(), rm2.getAuthor()); + assertEquals(c2.getCommitId(), rm2.getCommitId()); + assertEquals(c2.getMessage(), rm2.getMessage()); + assertEquals(c2.getTree().getTreeId(), rm2.getTree().getTreeId()); + assertEquals(1, rm2.getParentIds().length); + assertEquals(c1.getCommitId(), rm2.getParentIds()[0]); + + final Commit c3 = new Commit(db); + c3.setAuthor(new PersonIdent(jauthor, 1154236443000L, -4 * 60)); + c3.setCommitter(new PersonIdent(jcommitter, 1154236443000L, -4 * 60)); + c3.setMessage("A Commit 3\n"); + c3.setTree(t); + assertEquals(t.getTreeId(), c3.getTreeId()); + c3.setParentIds(new ObjectId[] { c1.getCommitId(), c2.getCommitId() }); + c3.commit(); + final ObjectId cmtid3 = ObjectId.fromString( + "ce6e1ce48fbeeb15a83f628dc8dc2debefa066f4"); + assertEquals(cmtid3, c3.getCommitId()); + + Commit rm3 = db.mapCommit(cmtid3); + assertNotSame(c3, rm3); // assert the parsed objects is not from the cache + assertEquals(c3.getAuthor(), rm3.getAuthor()); + assertEquals(c3.getCommitId(), rm3.getCommitId()); + assertEquals(c3.getMessage(), rm3.getMessage()); + assertEquals(c3.getTree().getTreeId(), rm3.getTree().getTreeId()); + assertEquals(2, rm3.getParentIds().length); + assertEquals(c1.getCommitId(), rm3.getParentIds()[0]); + assertEquals(c2.getCommitId(), rm3.getParentIds()[1]); + + final Commit c4 = new Commit(db); + c4.setAuthor(new PersonIdent(jauthor, 1154236443000L, -4 * 60)); + c4.setCommitter(new PersonIdent(jcommitter, 1154236443000L, -4 * 60)); + c4.setMessage("A Commit 4\n"); + c4.setTree(t); + assertEquals(t.getTreeId(), c3.getTreeId()); + c4.setParentIds(new ObjectId[] { c1.getCommitId(), c2.getCommitId(), c3.getCommitId() }); + c4.commit(); + final ObjectId cmtid4 = ObjectId.fromString( + "d1fca9fe3fef54e5212eb67902c8ed3e79736e27"); + assertEquals(cmtid4, c4.getCommitId()); + + Commit rm4 = db.mapCommit(cmtid4); + assertNotSame(c4, rm3); // assert the parsed objects is not from the cache + assertEquals(c4.getAuthor(), rm4.getAuthor()); + assertEquals(c4.getCommitId(), rm4.getCommitId()); + assertEquals(c4.getMessage(), rm4.getMessage()); + assertEquals(c4.getTree().getTreeId(), rm4.getTree().getTreeId()); + assertEquals(3, rm4.getParentIds().length); + assertEquals(c1.getCommitId(), rm4.getParentIds()[0]); + assertEquals(c2.getCommitId(), rm4.getParentIds()[1]); + assertEquals(c3.getCommitId(), rm4.getParentIds()[2]); + } + + public void test027_UnpackedRefHigherPriorityThanPacked() throws IOException { + PrintWriter writer = new PrintWriter(new FileWriter(new File(db.getDirectory(), "refs/heads/a"))); + String unpackedId = "7f822839a2fe9760f386cbbbcb3f92c5fe81def7"; + writer.print(unpackedId); + writer.print('\n'); + writer.close(); + + ObjectId resolved = db.resolve("refs/heads/a"); + assertEquals(unpackedId, resolved.name()); + } + + public void test028_LockPackedRef() throws IOException { + writeTrashFile(".git/packed-refs", "7f822839a2fe9760f386cbbbcb3f92c5fe81def7 refs/heads/foobar"); + writeTrashFile(".git/HEAD", "ref: refs/heads/foobar\n"); + + ObjectId resolve = db.resolve("HEAD"); + assertEquals("7f822839a2fe9760f386cbbbcb3f92c5fe81def7", resolve.name()); + + RefUpdate lockRef = db.updateRef("HEAD"); + ObjectId newId = ObjectId.fromString("07f822839a2fe9760f386cbbbcb3f92c5fe81def"); + lockRef.setNewObjectId(newId); + assertEquals(RefUpdate.Result.FORCED, lockRef.forceUpdate()); + + assertTrue(new File(db.getDirectory(), "refs/heads/foobar").exists()); + assertEquals(newId, db.resolve("refs/heads/foobar")); + + // Again. The ref already exists + RefUpdate lockRef2 = db.updateRef("HEAD"); + ObjectId newId2 = ObjectId.fromString("7f822839a2fe9760f386cbbbcb3f92c5fe81def7"); + lockRef2.setNewObjectId(newId2); + assertEquals(RefUpdate.Result.FORCED, lockRef2.forceUpdate()); + + assertTrue(new File(db.getDirectory(), "refs/heads/foobar").exists()); + assertEquals(newId2, db.resolve("refs/heads/foobar")); + } + + public void test029_mapObject() throws IOException { + assertEquals(new byte[0].getClass(), db.mapObject(ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"), null).getClass()); + assertEquals(Commit.class, db.mapObject(ObjectId.fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"), null).getClass()); + assertEquals(Tree.class, db.mapObject(ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"), null).getClass()); + assertEquals(Tag.class, db.mapObject(ObjectId.fromString("17768080a2318cd89bba4c8b87834401e2095703"), null).getClass()); + } + + public void test30_stripWorkDir() { + File relCwd = new File("."); + File absCwd = relCwd.getAbsoluteFile(); + File absBase = new File(new File(absCwd, "repo"), "workdir"); + File relBase = new File(new File(relCwd, "repo"), "workdir"); + assertEquals(absBase.getAbsolutePath(), relBase.getAbsolutePath()); + + File relBaseFile = new File(new File(relBase, "other"), "module.c"); + File absBaseFile = new File(new File(absBase, "other"), "module.c"); + assertEquals("other/module.c", Repository.stripWorkDir(relBase, relBaseFile)); + assertEquals("other/module.c", Repository.stripWorkDir(relBase, absBaseFile)); + assertEquals("other/module.c", Repository.stripWorkDir(absBase, relBaseFile)); + assertEquals("other/module.c", Repository.stripWorkDir(absBase, absBaseFile)); + + File relNonFile = new File(new File(relCwd, "not-repo"), ".gitignore"); + File absNonFile = new File(new File(absCwd, "not-repo"), ".gitignore"); + assertEquals("", Repository.stripWorkDir(relBase, relNonFile)); + assertEquals("", Repository.stripWorkDir(absBase, absNonFile)); + + assertEquals("", Repository.stripWorkDir(db.getWorkDir(), db.getWorkDir())); + + File file = new File(new File(db.getWorkDir(), "subdir"), "File.java"); + assertEquals("subdir/File.java", Repository.stripWorkDir(db.getWorkDir(), file)); + + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java new file mode 100644 index 000000000..adddbfe09 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0004_PackReader.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Imran M Yousuf + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.util.JGitTestUtil; + +public class T0004_PackReader extends RepositoryTestCase { + private static final String PACK_NAME = "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f"; + private static final File TEST_PACK = JGitTestUtil.getTestResourceFile(PACK_NAME + ".pack"); + private static final File TEST_IDX = JGitTestUtil.getTestResourceFile(PACK_NAME + ".idx"); + + public void test003_lookupCompressedObject() throws IOException { + final PackFile pr; + final ObjectId id; + final PackedObjectLoader or; + + id = ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"); + pr = new PackFile(TEST_IDX, TEST_PACK); + or = pr.get(new WindowCursor(), id); + assertNotNull(or); + assertEquals(Constants.OBJ_TREE, or.getType()); + assertEquals(35, or.getSize()); + assertEquals(7738, or.getDataOffset()); + pr.close(); + } + + public void test004_lookupDeltifiedObject() throws IOException { + final ObjectId id; + final ObjectLoader or; + + id = ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"); + or = db.openObject(id); + assertNotNull(or); + assertTrue(or instanceof PackedObjectLoader); + assertEquals(Constants.OBJ_BLOB, or.getType()); + assertEquals(18009, or.getSize()); + assertEquals(537, ((PackedObjectLoader) or).getDataOffset()); + } + + public void test005_todopack() throws IOException { + final File todopack = JGitTestUtil.getTestResourceFile("todopack"); + if (!todopack.isDirectory()) { + System.err.println("Skipping " + getName() + ": no " + todopack); + return; + } + + final File packDir = new File(db.getObjectsDirectory(), "pack"); + final String packname = "pack-2e71952edc41f3ce7921c5e5dd1b64f48204cf35"; + copyFile(new File(todopack, packname + ".pack"), new File(packDir, + packname + ".pack")); + copyFile(new File(todopack, packname + ".idx"), new File(packDir, + packname + ".idx")); + Tree t; + + t = db + .mapTree(ObjectId.fromString( + "aac9df07f653dd18b935298deb813e02c32d2e6f")); + assertNotNull(t); + t.memberCount(); + + t = db + .mapTree(ObjectId.fromString( + "6b9ffbebe7b83ac6a61c9477ab941d999f5d0c96")); + assertNotNull(t); + t.memberCount(); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0007_Index.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0007_Index.java new file mode 100644 index 000000000..74e4bfbde --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0007_Index.java @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2007-2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.eclipse.jgit.lib.GitIndex.Entry; +import org.eclipse.jgit.util.FS; + +public class T0007_Index extends RepositoryTestCase { + + static boolean canrungitstatus; + static { + try { + canrungitstatus = system(new File("."),"git --version") == 0; + } catch (IOException e) { + System.out.println("Warning: cannot invoke native git to validate index"); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + private static int system(File dir, String cmd) throws IOException, + InterruptedException { + final Process process = Runtime.getRuntime().exec(cmd, null, dir); + new Thread() { + public void run() { + try { + InputStream s = process.getErrorStream(); + for (int c = s.read(); c != -1; c = s.read()) { + System.err.print((char) c); + } + s.close(); + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + } + }.start(); + final Thread t2 = new Thread() { + public void run() { + synchronized (this) { + try { + InputStream e = process.getInputStream(); + for (int c = e.read(); c != -1; c = e.read()) { + System.out.print((char) c); + } + e.close(); + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + } + } + }; + t2.start(); + process.getOutputStream().close(); + int ret = process.waitFor(); + synchronized (t2) { + return ret; + } + } + + public void testCreateEmptyIndex() throws Exception { + GitIndex index = new GitIndex(db); + index.write(); +// native git doesn't like an empty index +// assertEquals(0,system(trash,"git status")); + + GitIndex indexr = new GitIndex(db); + indexr.read(); + assertEquals(0, indexr.getMembers().length); + } + + public void testReadWithNoIndex() throws Exception { + GitIndex index = new GitIndex(db); + index.read(); + assertEquals(0, index.getMembers().length); + } + + public void testCreateSimpleSortTestIndex() throws Exception { + GitIndex index = new GitIndex(db); + writeTrashFile("a/b", "data:a/b"); + writeTrashFile("a:b", "data:a:b"); + writeTrashFile("a.b", "data:a.b"); + index.add(trash, new File(trash, "a/b")); + index.add(trash, new File(trash, "a:b")); + index.add(trash, new File(trash, "a.b")); + index.write(); + + assertEquals("a/b", index.getEntry("a/b").getName()); + assertEquals("a:b", index.getEntry("a:b").getName()); + assertEquals("a.b", index.getEntry("a.b").getName()); + assertNull(index.getEntry("a*b")); + + // Repeat test for re-read index + GitIndex indexr = new GitIndex(db); + indexr.read(); + assertEquals("a/b", indexr.getEntry("a/b").getName()); + assertEquals("a:b", indexr.getEntry("a:b").getName()); + assertEquals("a.b", indexr.getEntry("a.b").getName()); + assertNull(indexr.getEntry("a*b")); + + if (canrungitstatus) + assertEquals(0, system(trash, "git status")); + } + + public void testUpdateSimpleSortTestIndex() throws Exception { + GitIndex index = new GitIndex(db); + writeTrashFile("a/b", "data:a/b"); + writeTrashFile("a:b", "data:a:b"); + writeTrashFile("a.b", "data:a.b"); + index.add(trash, new File(trash, "a/b")); + index.add(trash, new File(trash, "a:b")); + index.add(trash, new File(trash, "a.b")); + writeTrashFile("a/b", "data:a/b modified"); + index.add(trash, new File(trash, "a/b")); + index.write(); + if (canrungitstatus) + assertEquals(0, system(trash, "git status")); + } + + public void testWriteTree() throws Exception { + GitIndex index = new GitIndex(db); + writeTrashFile("a/b", "data:a/b"); + writeTrashFile("a:b", "data:a:b"); + writeTrashFile("a.b", "data:a.b"); + index.add(trash, new File(trash, "a/b")); + index.add(trash, new File(trash, "a:b")); + index.add(trash, new File(trash, "a.b")); + index.write(); + + ObjectId id = index.writeTree(); + assertEquals("c696abc3ab8e091c665f49d00eb8919690b3aec3", id.name()); + + writeTrashFile("a/b", "data:a/b"); + index.add(trash, new File(trash, "a/b")); + + if (canrungitstatus) + assertEquals(0, system(trash, "git status")); + } + + public void testReadTree() throws Exception { + // Prepare tree + GitIndex index = new GitIndex(db); + writeTrashFile("a/b", "data:a/b"); + writeTrashFile("a:b", "data:a:b"); + writeTrashFile("a.b", "data:a.b"); + index.add(trash, new File(trash, "a/b")); + index.add(trash, new File(trash, "a:b")); + index.add(trash, new File(trash, "a.b")); + index.write(); + + ObjectId id = index.writeTree(); + System.out.println("wrote id " + id); + assertEquals("c696abc3ab8e091c665f49d00eb8919690b3aec3", id.name()); + GitIndex index2 = new GitIndex(db); + + index2.readTree(db.mapTree(ObjectId.fromString( + "c696abc3ab8e091c665f49d00eb8919690b3aec3"))); + Entry[] members = index2.getMembers(); + assertEquals(3, members.length); + assertEquals("a.b", members[0].getName()); + assertEquals("a/b", members[1].getName()); + assertEquals("a:b", members[2].getName()); + assertEquals(3, members.length); + + GitIndex indexr = new GitIndex(db); + indexr.read(); + Entry[] membersr = indexr.getMembers(); + assertEquals(3, membersr.length); + assertEquals("a.b", membersr[0].getName()); + assertEquals("a/b", membersr[1].getName()); + assertEquals("a:b", membersr[2].getName()); + assertEquals(3, membersr.length); + + if (canrungitstatus) + assertEquals(0, system(trash, "git status")); + } + + public void testReadTree2() throws Exception { + // Prepare a larger tree to test some odd cases in tree writing + GitIndex index = new GitIndex(db); + File f1 = writeTrashFile("a/a/a/a", "data:a/a/a/a"); + File f2 = writeTrashFile("a/c/c", "data:a/c/c"); + File f3 = writeTrashFile("a/b", "data:a/b"); + File f4 = writeTrashFile("a:b", "data:a:b"); + File f5 = writeTrashFile("a/d", "data:a/d"); + File f6 = writeTrashFile("a.b", "data:a.b"); + index.add(trash, f1); + index.add(trash, f2); + index.add(trash, f3); + index.add(trash, f4); + index.add(trash, f5); + index.add(trash, f6); + index.write(); + ObjectId id = index.writeTree(); + System.out.println("wrote id " + id); + assertEquals("ba78e065e2c261d4f7b8f42107588051e87e18e9", id.name()); + GitIndex index2 = new GitIndex(db); + + index2.readTree(db.mapTree(ObjectId.fromString( + "ba78e065e2c261d4f7b8f42107588051e87e18e9"))); + Entry[] members = index2.getMembers(); + assertEquals(6, members.length); + assertEquals("a.b", members[0].getName()); + assertEquals("a/a/a/a", members[1].getName()); + assertEquals("a/b", members[2].getName()); + assertEquals("a/c/c", members[3].getName()); + assertEquals("a/d", members[4].getName()); + assertEquals("a:b", members[5].getName()); + + // reread and test + GitIndex indexr = new GitIndex(db); + indexr.read(); + Entry[] membersr = indexr.getMembers(); + assertEquals(6, membersr.length); + assertEquals("a.b", membersr[0].getName()); + assertEquals("a/a/a/a", membersr[1].getName()); + assertEquals("a/b", membersr[2].getName()); + assertEquals("a/c/c", membersr[3].getName()); + assertEquals("a/d", membersr[4].getName()); + assertEquals("a:b", membersr[5].getName()); + } + + public void testDelete() throws Exception { + GitIndex index = new GitIndex(db); + writeTrashFile("a/b", "data:a/b"); + writeTrashFile("a:b", "data:a:b"); + writeTrashFile("a.b", "data:a.b"); + index.add(trash, new File(trash, "a/b")); + index.add(trash, new File(trash, "a:b")); + index.add(trash, new File(trash, "a.b")); + index.write(); + index.writeTree(); + index.remove(trash, new File(trash, "a:b")); + index.write(); + assertEquals("a.b", index.getMembers()[0].getName()); + assertEquals("a/b", index.getMembers()[1].getName()); + + GitIndex indexr = new GitIndex(db); + indexr.read(); + assertEquals("a.b", indexr.getMembers()[0].getName()); + assertEquals("a/b", indexr.getMembers()[1].getName()); + + if (canrungitstatus) + assertEquals(0, system(trash, "git status")); + } + + public void testCheckout() throws Exception { + // Prepare tree, remote it and checkout + GitIndex index = new GitIndex(db); + File aslashb = writeTrashFile("a/b", "data:a/b"); + File acolonb = writeTrashFile("a:b", "data:a:b"); + File adotb = writeTrashFile("a.b", "data:a.b"); + index.add(trash, aslashb); + index.add(trash, acolonb); + index.add(trash, adotb); + index.write(); + index.writeTree(); + delete(aslashb); + delete(acolonb); + delete(adotb); + delete(aslashb.getParentFile()); + + GitIndex index2 = new GitIndex(db); + assertEquals(0, index2.getMembers().length); + + index2.readTree(db.mapTree(ObjectId.fromString( + "c696abc3ab8e091c665f49d00eb8919690b3aec3"))); + + index2.checkout(trash); + assertEquals("data:a/b", content(aslashb)); + assertEquals("data:a:b", content(acolonb)); + assertEquals("data:a.b", content(adotb)); + + if (canrungitstatus) + assertEquals(0, system(trash, "git status")); + } + + public void test030_executeBit_coreModeTrue() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, Error, Exception { + if (!FS.INSTANCE.supportsExecute()) { + System.err.println("Test ignored since platform FS does not support the execute permission"); + return; + } + try { + // coremode true is the default, typically set to false + // by git init (but not jgit!) + Method canExecute = File.class.getMethod("canExecute", (Class[])null); + Method setExecute = File.class.getMethod("setExecutable", new Class[] { Boolean.TYPE }); + File execFile = writeTrashFile("exec","exec"); + if (!((Boolean)setExecute.invoke(execFile, new Object[] { Boolean.TRUE })).booleanValue()) + throw new Error("could not set execute bit on "+execFile.getAbsolutePath()+"for test"); + File nonexecFile = writeTrashFile("nonexec","nonexec"); + if (!((Boolean)setExecute.invoke(nonexecFile, new Object[] { Boolean.FALSE })).booleanValue()) + throw new Error("could not clear execute bit on "+nonexecFile.getAbsolutePath()+"for test"); + + GitIndex index = new GitIndex(db); + index.filemode = Boolean.TRUE; // TODO: we need a way to set this using config + index.add(trash, execFile); + index.add(trash, nonexecFile); + Tree tree = db.mapTree(index.writeTree()); + assertEquals(FileMode.EXECUTABLE_FILE, tree.findBlobMember(execFile.getName()).getMode()); + assertEquals(FileMode.REGULAR_FILE, tree.findBlobMember(nonexecFile.getName()).getMode()); + + index.write(); + + if (!execFile.delete()) + throw new Error("Problem in test, cannot delete test file "+execFile.getAbsolutePath()); + if (!nonexecFile.delete()) + throw new Error("Problem in test, cannot delete test file "+nonexecFile.getAbsolutePath()); + GitIndex index2 = new GitIndex(db); + index2.filemode = Boolean.TRUE; // TODO: we need a way to set this using config + index2.read(); + index2.checkout(trash); + assertTrue(((Boolean)canExecute.invoke(execFile,(Object[])null)).booleanValue()); + assertFalse(((Boolean)canExecute.invoke(nonexecFile,(Object[])null)).booleanValue()); + + assertFalse(index2.getEntry(execFile.getName()).isModified(trash)); + assertFalse(index2.getEntry(nonexecFile.getName()).isModified(trash)); + + if (!((Boolean)setExecute.invoke(execFile, new Object[] { Boolean.FALSE })).booleanValue()) + throw new Error("could not clear set execute bit on "+execFile.getAbsolutePath()+"for test"); + if (!((Boolean)setExecute.invoke(nonexecFile, new Object[] { Boolean.TRUE })).booleanValue()) + throw new Error("could set execute bit on "+nonexecFile.getAbsolutePath()+"for test"); + + assertTrue(index2.getEntry(execFile.getName()).isModified(trash)); + assertTrue(index2.getEntry(nonexecFile.getName()).isModified(trash)); + + } catch (NoSuchMethodException e) { + System.err.println("Test ignored when running under JDK < 1.6"); + return; + } + } + + public void test031_executeBit_coreModeFalse() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, Error, Exception { + if (!FS.INSTANCE.supportsExecute()) { + System.err.println("Test ignored since platform FS does not support the execute permission"); + return; + } + try { + // coremode true is the default, typically set to false + // by git init (but not jgit!) + Method canExecute = File.class.getMethod("canExecute", (Class[])null); + Method setExecute = File.class.getMethod("setExecutable", new Class[] { Boolean.TYPE }); + File execFile = writeTrashFile("exec","exec"); + if (!((Boolean)setExecute.invoke(execFile, new Object[] { Boolean.TRUE })).booleanValue()) + throw new Error("could not set execute bit on "+execFile.getAbsolutePath()+"for test"); + File nonexecFile = writeTrashFile("nonexec","nonexec"); + if (!((Boolean)setExecute.invoke(nonexecFile, new Object[] { Boolean.FALSE })).booleanValue()) + throw new Error("could not clear execute bit on "+nonexecFile.getAbsolutePath()+"for test"); + + GitIndex index = new GitIndex(db); + index.filemode = Boolean.FALSE; // TODO: we need a way to set this using config + index.add(trash, execFile); + index.add(trash, nonexecFile); + Tree tree = db.mapTree(index.writeTree()); + assertEquals(FileMode.REGULAR_FILE, tree.findBlobMember(execFile.getName()).getMode()); + assertEquals(FileMode.REGULAR_FILE, tree.findBlobMember(nonexecFile.getName()).getMode()); + + index.write(); + + if (!execFile.delete()) + throw new Error("Problem in test, cannot delete test file "+execFile.getAbsolutePath()); + if (!nonexecFile.delete()) + throw new Error("Problem in test, cannot delete test file "+nonexecFile.getAbsolutePath()); + GitIndex index2 = new GitIndex(db); + index2.filemode = Boolean.FALSE; // TODO: we need a way to set this using config + index2.read(); + index2.checkout(trash); + assertFalse(((Boolean)canExecute.invoke(execFile,(Object[])null)).booleanValue()); + assertFalse(((Boolean)canExecute.invoke(nonexecFile,(Object[])null)).booleanValue()); + + assertFalse(index2.getEntry(execFile.getName()).isModified(trash)); + assertFalse(index2.getEntry(nonexecFile.getName()).isModified(trash)); + + if (!((Boolean)setExecute.invoke(execFile, new Object[] { Boolean.FALSE })).booleanValue()) + throw new Error("could not clear set execute bit on "+execFile.getAbsolutePath()+"for test"); + if (!((Boolean)setExecute.invoke(nonexecFile, new Object[] { Boolean.TRUE })).booleanValue()) + throw new Error("could set execute bit on "+nonexecFile.getAbsolutePath()+"for test"); + + // no change since we ignore the execute bit + assertFalse(index2.getEntry(execFile.getName()).isModified(trash)); + assertFalse(index2.getEntry(nonexecFile.getName()).isModified(trash)); + + } catch (NoSuchMethodException e) { + System.err.println("Test ignored when running under JDK < 1.6"); + return; + } + } + + private String content(File f) throws IOException { + byte[] buf = new byte[(int) f.length()]; + FileInputStream is = new FileInputStream(f); + try { + int read = is.read(buf); + assertEquals(f.length(), read); + return new String(buf, 0); + } finally { + is.close(); + } + } + + private void delete(File f) throws IOException { + if (!f.delete()) + throw new IOException("Failed to delete f"); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0008_testparserev.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0008_testparserev.java new file mode 100644 index 000000000..ac730ff6a --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0008_testparserev.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2008, Jonas Fonseca + * Copyright (C) 2007-2008, Robin Rosenberg + * Copyright (C) 2006, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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; + +public class T0008_testparserev extends RepositoryTestCase { + + public void testObjectId_existing() throws IOException { + assertEquals("49322bb17d3acc9146f98c97d078513228bbf3c0",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0").name()); + } + + public void testObjectId_nonexisting() throws IOException { + assertEquals("49322bb17d3acc9146f98c97d078513228bbf3c1",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c1").name()); + } + + public void testObjectId_objectid_implicit_firstparent() throws IOException { + assertEquals("6e1475206e57110fcef4b92320436c1e9872a322",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^").name()); + assertEquals("1203b03dc816ccbb67773f28b3c19318654b0bc8",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^^").name()); + assertEquals("bab66b48f836ed950c99134ef666436fb07a09a0",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^^^").name()); + } + + public void testObjectId_objectid_self() throws IOException { + assertEquals("49322bb17d3acc9146f98c97d078513228bbf3c0",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^0").name()); + assertEquals("49322bb17d3acc9146f98c97d078513228bbf3c0",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^0^0").name()); + assertEquals("49322bb17d3acc9146f98c97d078513228bbf3c0",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^0^0^0").name()); + } + + public void testObjectId_objectid_explicit_firstparent() throws IOException { + assertEquals("6e1475206e57110fcef4b92320436c1e9872a322",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^1").name()); + assertEquals("1203b03dc816ccbb67773f28b3c19318654b0bc8",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^1^1").name()); + assertEquals("bab66b48f836ed950c99134ef666436fb07a09a0",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^1^1^1").name()); + } + + public void testObjectId_objectid_explicit_otherparents() throws IOException { + assertEquals("6e1475206e57110fcef4b92320436c1e9872a322",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^1").name()); + assertEquals("f73b95671f326616d66b2afb3bdfcdbbce110b44",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^2").name()); + assertEquals("d0114ab8ac326bab30e3a657a0397578c5a1af88",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^3").name()); + assertEquals("d0114ab8ac326bab30e3a657a0397578c5a1af88",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^03").name()); + } + + public void testRef_refname() throws IOException { + assertEquals("49322bb17d3acc9146f98c97d078513228bbf3c0",db.resolve("master^0").name()); + assertEquals("6e1475206e57110fcef4b92320436c1e9872a322",db.resolve("master^").name()); + assertEquals("6e1475206e57110fcef4b92320436c1e9872a322",db.resolve("refs/heads/master^1").name()); + } + + public void testDistance() throws IOException { + assertEquals("49322bb17d3acc9146f98c97d078513228bbf3c0",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0~0").name()); + assertEquals("6e1475206e57110fcef4b92320436c1e9872a322",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0~1").name()); + assertEquals("1203b03dc816ccbb67773f28b3c19318654b0bc8",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0~2").name()); + assertEquals("bab66b48f836ed950c99134ef666436fb07a09a0",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0~3").name()); + assertEquals("bab66b48f836ed950c99134ef666436fb07a09a0",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0~03").name()); + } + + public void testTree() throws IOException { + assertEquals("6020a3b8d5d636e549ccbd0c53e2764684bb3125",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^{tree}").name()); + assertEquals("02ba32d3649e510002c21651936b7077aa75ffa9",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^^{tree}").name()); + } + + public void testHEAD() throws IOException { + assertEquals("6020a3b8d5d636e549ccbd0c53e2764684bb3125",db.resolve("HEAD^{tree}").name()); + } + + public void testDerefCommit() throws IOException { + assertEquals("49322bb17d3acc9146f98c97d078513228bbf3c0",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^{}").name()); + assertEquals("49322bb17d3acc9146f98c97d078513228bbf3c0",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^{commit}").name()); + // double deref + assertEquals("6020a3b8d5d636e549ccbd0c53e2764684bb3125",db.resolve("49322bb17d3acc9146f98c97d078513228bbf3c0^{commit}^{tree}").name()); + } + + public void testDerefTag() throws IOException { + assertEquals("17768080a2318cd89bba4c8b87834401e2095703",db.resolve("refs/tags/B").name()); + assertEquals("d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864",db.resolve("refs/tags/B^{commit}").name()); + assertEquals("032c063ce34486359e3ee3d4f9e5c225b9e1a4c2",db.resolve("refs/tags/B10th").name()); + assertEquals("d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864",db.resolve("refs/tags/B10th^{commit}").name()); + assertEquals("d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864",db.resolve("refs/tags/B10th^{}").name()); + assertEquals("d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864",db.resolve("refs/tags/B10th^0").name()); + assertEquals("d86a2aada2f5e7ccf6f11880bfb9ab404e8a8864",db.resolve("refs/tags/B10th~0").name()); + assertEquals("0966a434eb1a025db6b71485ab63a3bfbea520b6",db.resolve("refs/tags/B10th^").name()); + assertEquals("0966a434eb1a025db6b71485ab63a3bfbea520b6",db.resolve("refs/tags/B10th^1").name()); + assertEquals("0966a434eb1a025db6b71485ab63a3bfbea520b6",db.resolve("refs/tags/B10th~1").name()); + assertEquals("2c349335b7f797072cf729c4f3bb0914ecb6dec9",db.resolve("refs/tags/B10th~2").name()); + } + + public void testDerefBlob() throws IOException { + assertEquals("fd608fbe625a2b456d9f15c2b1dc41f252057dd7",db.resolve("spearce-gpg-pub^{}").name()); + assertEquals("fd608fbe625a2b456d9f15c2b1dc41f252057dd7",db.resolve("spearce-gpg-pub^{blob}").name()); + assertEquals("fd608fbe625a2b456d9f15c2b1dc41f252057dd7",db.resolve("fd608fbe625a2b456d9f15c2b1dc41f252057dd7^{}").name()); + assertEquals("fd608fbe625a2b456d9f15c2b1dc41f252057dd7",db.resolve("fd608fbe625a2b456d9f15c2b1dc41f252057dd7^{blob}").name()); + } + + public void testDerefTree() throws IOException { + assertEquals("032c063ce34486359e3ee3d4f9e5c225b9e1a4c2",db.resolve("refs/tags/B10th").name()); + assertEquals("856ec208ae6cadac25a6d74f19b12bb27a24fe24",db.resolve("032c063ce34486359e3ee3d4f9e5c225b9e1a4c2^{tree}").name()); + assertEquals("856ec208ae6cadac25a6d74f19b12bb27a24fe24",db.resolve("refs/tags/B10th^{tree}").name()); + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/TreeIteratorLeafOnlyTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/TreeIteratorLeafOnlyTest.java new file mode 100644 index 000000000..6b19cc1e8 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/TreeIteratorLeafOnlyTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2006, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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; + +public class TreeIteratorLeafOnlyTest extends RepositoryTestCase { + + /** Empty tree */ + public void testEmpty() { + Tree tree = new Tree(db); + TreeIterator i = makeIterator(tree); + assertFalse(i.hasNext()); + } + + /** + * one file + * + * @throws IOException + */ + public void testSimpleF1() throws IOException { + Tree tree = new Tree(db); + tree.addFile("x"); + TreeIterator i = makeIterator(tree); + assertTrue(i.hasNext()); + assertEquals("x", i.next().getName()); + } + + /** + * two files + * + * @throws IOException + */ + public void testSimpleF2() throws IOException { + Tree tree = new Tree(db); + tree.addFile("a"); + tree.addFile("x"); + TreeIterator i = makeIterator(tree); + assertTrue(i.hasNext()); + assertEquals("a", i.next().getName()); + assertEquals("x", i.next().getName()); + } + + /** + * Empty tree + * + * @throws IOException + */ + public void testSimpleT() throws IOException { + Tree tree = new Tree(db); + tree.addTree("a"); + TreeIterator i = makeIterator(tree); + assertFalse(i.hasNext()); + } + + public void testTricky() throws IOException { + Tree tree = new Tree(db); + tree.addFile("a.b"); + tree.addFile("a.c"); + tree.addFile("a/b.b/b"); + tree.addFile("a/b"); + tree.addFile("a/c"); + tree.addFile("a=c"); + tree.addFile("a=d"); + + TreeIterator i = makeIterator(tree); + assertTrue(i.hasNext()); + assertEquals("a.b", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a.c", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a/b", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a/b.b/b", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a/c", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a=c", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a=d", i.next().getFullName()); + assertFalse(i.hasNext()); + } + + private TreeIterator makeIterator(Tree tree) { + return new TreeIterator(tree); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/TreeIteratorPostOrderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/TreeIteratorPostOrderTest.java new file mode 100644 index 000000000..caf8bff2a --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/TreeIteratorPostOrderTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2006, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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; + +public class TreeIteratorPostOrderTest extends RepositoryTestCase { + + /** Empty tree */ + public void testEmpty() { + Tree tree = new Tree(db); + TreeIterator i = makeIterator(tree); + assertTrue(i.hasNext()); + assertEquals("", i.next().getFullName()); + assertFalse(i.hasNext()); + } + + /** + * one file + * + * @throws IOException + */ + public void testSimpleF1() throws IOException { + Tree tree = new Tree(db); + tree.addFile("x"); + TreeIterator i = makeIterator(tree); + assertTrue(i.hasNext()); + assertEquals("x", i.next().getName()); + assertTrue(i.hasNext()); + assertEquals("", i.next().getFullName()); + assertFalse(i.hasNext()); + } + + /** + * two files + * + * @throws IOException + */ + public void testSimpleF2() throws IOException { + Tree tree = new Tree(db); + tree.addFile("a"); + tree.addFile("x"); + TreeIterator i = makeIterator(tree); + assertTrue(i.hasNext()); + assertEquals("a", i.next().getName()); + assertEquals("x", i.next().getName()); + assertTrue(i.hasNext()); + assertEquals("", i.next().getFullName()); + assertFalse(i.hasNext()); + } + + /** + * Empty tree + * + * @throws IOException + */ + public void testSimpleT() throws IOException { + Tree tree = new Tree(db); + tree.addTree("a"); + TreeIterator i = makeIterator(tree); + assertTrue(i.hasNext()); + assertEquals("a", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("", i.next().getFullName()); + assertFalse(i.hasNext()); + } + + public void testTricky() throws IOException { + Tree tree = new Tree(db); + tree.addFile("a.b"); + tree.addFile("a.c"); + tree.addFile("a/b.b/b"); + tree.addFile("a/b"); + tree.addFile("a/c"); + tree.addFile("a=c"); + tree.addFile("a=d"); + + TreeIterator i = makeIterator(tree); + assertTrue(i.hasNext()); + assertEquals("a.b", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a.c", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a/b", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a/b.b/b", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a/b.b", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a/c", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a=c", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a=d", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("", i.next().getFullName()); + assertFalse(i.hasNext()); + } + + private TreeIterator makeIterator(Tree tree) { + return new TreeIterator(tree, TreeIterator.Order.POSTORDER); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/TreeIteratorPreOrderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/TreeIteratorPreOrderTest.java new file mode 100644 index 000000000..c6f41446d --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/TreeIteratorPreOrderTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2006, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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; + +public class TreeIteratorPreOrderTest extends RepositoryTestCase { + + /** Empty tree */ + public void testEmpty() { + Tree tree = new Tree(db); + TreeIterator i = makeIterator(tree); + assertTrue(i.hasNext()); + assertEquals("", i.next().getFullName()); + assertFalse(i.hasNext()); + } + + /** + * one file + * + * @throws IOException + */ + public void testSimpleF1() throws IOException { + Tree tree = new Tree(db); + tree.addFile("x"); + TreeIterator i = makeIterator(tree); + assertTrue(i.hasNext()); + assertEquals("", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("x", i.next().getName()); + assertFalse(i.hasNext()); + } + + /** + * two files + * + * @throws IOException + */ + public void testSimpleF2() throws IOException { + Tree tree = new Tree(db); + tree.addFile("a"); + tree.addFile("x"); + TreeIterator i = makeIterator(tree); + assertTrue(i.hasNext()); + assertEquals("", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a", i.next().getName()); + assertEquals("x", i.next().getName()); + assertFalse(i.hasNext()); + } + + /** + * Empty tree + * + * @throws IOException + */ + public void testSimpleT() throws IOException { + Tree tree = new Tree(db); + tree.addTree("a"); + TreeIterator i = makeIterator(tree); + assertTrue(i.hasNext()); + assertEquals("", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a", i.next().getFullName()); + assertFalse(i.hasNext()); + } + + public void testTricky() throws IOException { + Tree tree = new Tree(db); + tree.addFile("a.b"); + tree.addFile("a.c"); + tree.addFile("a/b.b/b"); + tree.addFile("a/b"); + tree.addFile("a/c"); + tree.addFile("a=c"); + tree.addFile("a=d"); + + TreeIterator i = makeIterator(tree); + assertTrue(i.hasNext()); + assertEquals("", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a.b", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a.c", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a/b", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a/b.b", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a/b.b/b", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a/c", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a=c", i.next().getFullName()); + assertTrue(i.hasNext()); + assertEquals("a=d", i.next().getFullName()); + assertFalse(i.hasNext()); + } + + private TreeIterator makeIterator(Tree tree) { + return new TreeIterator(tree, TreeIterator.Order.PREORDER); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ValidRefNameTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ValidRefNameTest.java new file mode 100644 index 000000000..79e2eba70 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ValidRefNameTest.java @@ -0,0 +1,177 @@ +/* + * 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 junit.framework.TestCase; + +public class ValidRefNameTest extends TestCase { + private static void assertValid(final boolean exp, final String name) { + assertEquals("\"" + name + "\"", exp, Repository.isValidRefName(name)); + } + + public void testEmptyString() { + assertValid(false, ""); + assertValid(false, "/"); + } + + public void testMustHaveTwoComponents() { + assertValid(false, "master"); + assertValid(true, "heads/master"); + } + + public void testValidHead() { + assertValid(true, "refs/heads/master"); + assertValid(true, "refs/heads/pu"); + assertValid(true, "refs/heads/z"); + assertValid(true, "refs/heads/FoO"); + } + + public void testValidTag() { + assertValid(true, "refs/tags/v1.0"); + } + + public void testNoLockSuffix() { + assertValid(false, "refs/heads/master.lock"); + } + + public void testNoDirectorySuffix() { + assertValid(false, "refs/heads/master/"); + } + + public void testNoSpace() { + assertValid(false, "refs/heads/i haz space"); + } + + public void testNoAsciiControlCharacters() { + for (char c = '\0'; c < ' '; c++) + assertValid(false, "refs/heads/mast" + c + "er"); + } + + public void testNoBareDot() { + assertValid(false, "refs/heads/."); + assertValid(false, "refs/heads/.."); + assertValid(false, "refs/heads/./master"); + assertValid(false, "refs/heads/../master"); + } + + public void testNoLeadingOrTrailingDot() { + assertValid(false, "."); + assertValid(false, "refs/heads/.bar"); + assertValid(false, "refs/heads/..bar"); + assertValid(false, "refs/heads/bar."); + } + + public void testContainsDot() { + assertValid(true, "refs/heads/m.a.s.t.e.r"); + assertValid(false, "refs/heads/master..pu"); + } + + public void testNoMagicRefCharacters() { + assertValid(false, "refs/heads/master^"); + assertValid(false, "refs/heads/^master"); + assertValid(false, "^refs/heads/master"); + + assertValid(false, "refs/heads/master~"); + assertValid(false, "refs/heads/~master"); + assertValid(false, "~refs/heads/master"); + + assertValid(false, "refs/heads/master:"); + assertValid(false, "refs/heads/:master"); + assertValid(false, ":refs/heads/master"); + } + + public void testShellGlob() { + assertValid(false, "refs/heads/master?"); + assertValid(false, "refs/heads/?master"); + assertValid(false, "?refs/heads/master"); + + assertValid(false, "refs/heads/master["); + assertValid(false, "refs/heads/[master"); + assertValid(false, "[refs/heads/master"); + + assertValid(false, "refs/heads/master*"); + assertValid(false, "refs/heads/*master"); + assertValid(false, "*refs/heads/master"); + } + + public void testValidSpecialCharacters() { + assertValid(true, "refs/heads/!"); + assertValid(true, "refs/heads/\""); + assertValid(true, "refs/heads/#"); + assertValid(true, "refs/heads/$"); + assertValid(true, "refs/heads/%"); + assertValid(true, "refs/heads/&"); + assertValid(true, "refs/heads/'"); + assertValid(true, "refs/heads/("); + assertValid(true, "refs/heads/)"); + assertValid(true, "refs/heads/+"); + assertValid(true, "refs/heads/,"); + assertValid(true, "refs/heads/-"); + assertValid(true, "refs/heads/;"); + assertValid(true, "refs/heads/<"); + assertValid(true, "refs/heads/="); + assertValid(true, "refs/heads/>"); + assertValid(true, "refs/heads/@"); + assertValid(true, "refs/heads/]"); + assertValid(true, "refs/heads/_"); + assertValid(true, "refs/heads/`"); + assertValid(true, "refs/heads/{"); + assertValid(true, "refs/heads/|"); + assertValid(true, "refs/heads/}"); + + // This is valid on UNIX, but not on Windows + // hence we make in invalid due to non-portability + // + assertValid(false, "refs/heads/\\"); + } + + public void testUnicodeNames() { + assertValid(true, "refs/heads/\u00e5ngstr\u00f6m"); + } + + public void testRefLogQueryIsValidRef() { + assertValid(false, "refs/heads/master@{1}"); + assertValid(false, "refs/heads/master@{1.hour.ago}"); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheGetTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheGetTest.java new file mode 100644 index 000000000..55b568d8f --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheGetTest.java @@ -0,0 +1,149 @@ +/* + * 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.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.util.JGitTestUtil; +import org.eclipse.jgit.util.MutableInteger; + +public class WindowCacheGetTest extends RepositoryTestCase { + private List toLoad; + + @Override + public void setUp() throws Exception { + super.setUp(); + + toLoad = new ArrayList(); + final BufferedReader br = new BufferedReader(new InputStreamReader( + new FileInputStream(JGitTestUtil + .getTestResourceFile("all_packed_objects.txt")), + Constants.CHARSET)); + try { + String line; + while ((line = br.readLine()) != null) { + final String[] parts = line.split(" {1,}"); + final TestObject o = new TestObject(); + o.id = ObjectId.fromString(parts[0]); + o.setType(parts[1]); + o.rawSize = Integer.parseInt(parts[2]); + // parts[3] is the size-in-pack + o.offset = Long.parseLong(parts[4]); + toLoad.add(o); + } + } finally { + br.close(); + } + assertEquals(96, toLoad.size()); + } + + public void testCache_Defaults() throws IOException { + final WindowCacheConfig cfg = new WindowCacheConfig(); + WindowCache.reconfigure(cfg); + doCacheTests(); + checkLimits(cfg); + + final WindowCache cache = WindowCache.getInstance(); + assertEquals(6, cache.getOpenFiles()); + assertEquals(17346, cache.getOpenBytes()); + } + + public void testCache_TooFewFiles() throws IOException { + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitOpenFiles(2); + WindowCache.reconfigure(cfg); + doCacheTests(); + checkLimits(cfg); + } + + public void testCache_TooSmallLimit() throws IOException { + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitWindowSize(4096); + cfg.setPackedGitLimit(4096); + WindowCache.reconfigure(cfg); + doCacheTests(); + checkLimits(cfg); + } + + private void checkLimits(final WindowCacheConfig cfg) { + final WindowCache cache = WindowCache.getInstance(); + assertTrue(cache.getOpenFiles() <= cfg.getPackedGitOpenFiles()); + assertTrue(cache.getOpenBytes() <= cfg.getPackedGitLimit()); + assertTrue(0 < cache.getOpenFiles()); + assertTrue(0 < cache.getOpenBytes()); + } + + private void doCacheTests() throws IOException { + for (final TestObject o : toLoad) { + final ObjectLoader or = db.openObject(o.id); + assertNotNull(or); + assertTrue(or instanceof PackedObjectLoader); + assertEquals(o.type, or.getType()); + assertEquals(o.rawSize, or.getRawSize()); + assertEquals(o.offset, ((PackedObjectLoader) or).getObjectOffset()); + } + } + + private class TestObject { + ObjectId id; + + int type; + + int rawSize; + + long offset; + + void setType(final String typeStr) throws CorruptObjectException { + final byte[] typeRaw = Constants.encode(typeStr + " "); + final MutableInteger ptr = new MutableInteger(); + type = Constants.decodeTypeString(id, typeRaw, (byte) ' ', ptr); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheReconfigureTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheReconfigureTest.java new file mode 100644 index 000000000..9e093c85b --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WindowCacheReconfigureTest.java @@ -0,0 +1,125 @@ +/* + * 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; + +public class WindowCacheReconfigureTest extends RepositoryTestCase { + public void testConfigureCache_PackedGitLimit_0() { + try { + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitLimit(0); + WindowCache.reconfigure(cfg); + fail("incorrectly permitted PackedGitLimit = 0"); + } catch (IllegalArgumentException e) { + // + } + } + + public void testConfigureCache_PackedGitWindowSize_0() { + try { + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitWindowSize(0); + WindowCache.reconfigure(cfg); + fail("incorrectly permitted PackedGitWindowSize = 0"); + } catch (IllegalArgumentException e) { + assertEquals("Invalid window size", e.getMessage()); + } + } + + public void testConfigureCache_PackedGitWindowSize_512() { + try { + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitWindowSize(512); + WindowCache.reconfigure(cfg); + fail("incorrectly permitted PackedGitWindowSize = 512"); + } catch (IllegalArgumentException e) { + assertEquals("Invalid window size", e.getMessage()); + } + } + + public void testConfigureCache_PackedGitWindowSize_4097() { + try { + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitWindowSize(4097); + WindowCache.reconfigure(cfg); + fail("incorrectly permitted PackedGitWindowSize = 4097"); + } catch (IllegalArgumentException e) { + assertEquals("Window size must be power of 2", e.getMessage()); + } + } + + public void testConfigureCache_PackedGitOpenFiles_0() { + try { + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitOpenFiles(0); + WindowCache.reconfigure(cfg); + fail("incorrectly permitted PackedGitOpenFiles = 0"); + } catch (IllegalArgumentException e) { + assertEquals("Open files must be >= 1", e.getMessage()); + } + } + + public void testConfigureCache_PackedGitWindowSizeAbovePackedGitLimit() { + try { + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitLimit(1024); + cfg.setPackedGitWindowSize(8192); + WindowCache.reconfigure(cfg); + fail("incorrectly permitted PackedGitWindowSize > PackedGitLimit"); + } catch (IllegalArgumentException e) { + assertEquals("Window size must be < limit", e.getMessage()); + } + } + + public void testConfigureCache_Limits1() { + // This test is just to force coverage over some lower bounds for + // the table. We don't want the table to wind up with too small + // of a size. This is highly dependent upon the table allocation + // algorithm actually implemented in WindowCache. + // + final WindowCacheConfig cfg = new WindowCacheConfig(); + cfg.setPackedGitLimit(6 * 4096 / 5); + cfg.setPackedGitWindowSize(4096); + WindowCache.reconfigure(cfg); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WorkDirCheckoutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WorkDirCheckoutTest.java new file mode 100644 index 000000000..2599468fa --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/WorkDirCheckoutTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2006, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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; + +import org.eclipse.jgit.errors.CheckoutConflictException; + +public class WorkDirCheckoutTest extends RepositoryTestCase { + public void testFindingConflicts() throws IOException { + GitIndex index = new GitIndex(db); + index.add(trash, writeTrashFile("bar", "bar")); + index.add(trash, writeTrashFile("foo/bar/baz/qux", "foo/bar")); + recursiveDelete(new File(trash, "bar")); + recursiveDelete(new File(trash, "foo")); + writeTrashFile("bar/baz/qux/foo", "another nasty one"); + writeTrashFile("foo", "troublesome little bugger"); + + WorkDirCheckout workDirCheckout = new WorkDirCheckout(db, trash, index, + index); + workDirCheckout.prescanOneTree(); + ArrayList conflictingEntries = workDirCheckout + .getConflicts(); + ArrayList removedEntries = workDirCheckout.getRemoved(); + assertEquals("bar/baz/qux/foo", conflictingEntries.get(0)); + assertEquals("foo", conflictingEntries.get(1)); + + GitIndex index2 = new GitIndex(db); + recursiveDelete(new File(trash, "bar")); + recursiveDelete(new File(trash, "foo")); + + index2.add(trash, writeTrashFile("bar/baz/qux/foo", "bar")); + index2.add(trash, writeTrashFile("foo", "lalala")); + + workDirCheckout = new WorkDirCheckout(db, trash, index2, index); + workDirCheckout.prescanOneTree(); + + conflictingEntries = workDirCheckout.getConflicts(); + removedEntries = workDirCheckout.getRemoved(); + assertTrue(conflictingEntries.isEmpty()); + assertTrue(removedEntries.contains("bar/baz/qux/foo")); + assertTrue(removedEntries.contains("foo")); + } + + public void testCheckingOutWithConflicts() throws IOException { + GitIndex index = new GitIndex(db); + index.add(trash, writeTrashFile("bar", "bar")); + index.add(trash, writeTrashFile("foo/bar/baz/qux", "foo/bar")); + recursiveDelete(new File(trash, "bar")); + recursiveDelete(new File(trash, "foo")); + writeTrashFile("bar/baz/qux/foo", "another nasty one"); + writeTrashFile("foo", "troublesome little bugger"); + + try { + WorkDirCheckout workDirCheckout = new WorkDirCheckout(db, trash, + index, index); + workDirCheckout.checkout(); + fail("Should have thrown exception"); + } catch (CheckoutConflictException e) { + // all is well + } + + WorkDirCheckout workDirCheckout = new WorkDirCheckout(db, trash, index, + index); + workDirCheckout.setFailOnConflict(false); + workDirCheckout.checkout(); + + assertTrue(new File(trash, "bar").isFile()); + assertTrue(new File(trash, "foo/bar/baz/qux").isFile()); + + GitIndex index2 = new GitIndex(db); + recursiveDelete(new File(trash, "bar")); + recursiveDelete(new File(trash, "foo")); + index2.add(trash, writeTrashFile("bar/baz/qux/foo", "bar")); + writeTrashFile("bar/baz/qux/bar", "evil? I thought it said WEEVIL!"); + index2.add(trash, writeTrashFile("foo", "lalala")); + + workDirCheckout = new WorkDirCheckout(db, trash, index2, index); + workDirCheckout.setFailOnConflict(false); + workDirCheckout.checkout(); + + assertTrue(new File(trash, "bar").isFile()); + assertTrue(new File(trash, "foo/bar/baz/qux").isFile()); + assertNotNull(index2.getEntry("bar")); + assertNotNull(index2.getEntry("foo/bar/baz/qux")); + assertNull(index2.getEntry("bar/baz/qux/foo")); + assertNull(index2.getEntry("foo")); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/XInputStream.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/XInputStream.java new file mode 100644 index 000000000..eef32b927 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/XInputStream.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.BufferedInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.jgit.util.NB; + +class XInputStream extends BufferedInputStream { + private final byte[] intbuf = new byte[8]; + + XInputStream(final InputStream s) { + super(s); + } + + synchronized byte[] readFully(final int len) throws IOException { + final byte[] b = new byte[len]; + readFully(b, 0, len); + return b; + } + + synchronized void readFully(final byte[] b, int o, int len) + throws IOException { + int r; + while (len > 0 && (r = read(b, o, len)) > 0) { + o += r; + len -= r; + } + if (len > 0) + throw new EOFException(); + } + + int readUInt8() throws IOException { + final int r = read(); + if (r < 0) + throw new EOFException(); + return r; + } + + long readUInt32() throws IOException { + readFully(intbuf, 0, 4); + return NB.decodeUInt32(intbuf, 0); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/empty.gitindex.dat b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/empty.gitindex.dat new file mode 100644 index 0000000000000000000000000000000000000000..3330d716f1541c5acb1ed77867020cc7d88df5b7 GIT binary patch literal 32 mcmZ?q402{*U|<4b2Fn{0gy%gq&8hoZq?q&itoka)pZ5Wd4h#_h literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/sorttest.gitindex.dat b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/sorttest.gitindex.dat new file mode 100644 index 0000000000000000000000000000000000000000..217f2e3811eac53b751d82131ac3700bce0283ff GIT binary patch literal 288 zcmZ?q402{*U|<4bX4hL9$AB~gjAmfqVCPcXI*EayaR~zh;}-~KV0bq7(dOY4}gUn%Cc1nv#bM)cnym-|HHU~gu zceRg4*V~wGVgAuCy6()&?Hi=a3xV#jf|(N%GfXk_T+q1c< oPv0-EaSe6|t+w#;VeHyIt<8 literal 0 HcmV?d00001 diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java new file mode 100644 index 000000000..42e653be3 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/CherryPickTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2008, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.ByteArrayInputStream; + +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.lib.Commit; +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.lib.PersonIdent; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.treewalk.TreeWalk; + +public class CherryPickTest extends RepositoryTestCase { + public void testPick() throws Exception { + // B---O + // \----P---T + // + // Cherry-pick "T" onto "O". This shouldn't introduce "p-fail", which + // was created by "P", nor should it modify "a", which was done by "P". + // + final DirCache treeB = DirCache.read(db); + final DirCache treeO = DirCache.read(db); + final DirCache treeP = DirCache.read(db); + final DirCache treeT = DirCache.read(db); + { + final DirCacheBuilder b = treeB.builder(); + final DirCacheBuilder o = treeO.builder(); + final DirCacheBuilder p = treeP.builder(); + final DirCacheBuilder t = treeT.builder(); + + b.add(makeEntry("a", FileMode.REGULAR_FILE)); + + o.add(makeEntry("a", FileMode.REGULAR_FILE)); + o.add(makeEntry("o", FileMode.REGULAR_FILE)); + + p.add(makeEntry("a", FileMode.REGULAR_FILE, "q")); + p.add(makeEntry("p-fail", FileMode.REGULAR_FILE)); + + t.add(makeEntry("a", FileMode.REGULAR_FILE)); + t.add(makeEntry("t", FileMode.REGULAR_FILE)); + + b.finish(); + o.finish(); + p.finish(); + t.finish(); + } + + final ObjectWriter ow = new ObjectWriter(db); + final ObjectId B = commit(ow, treeB, new ObjectId[] {}); + final ObjectId O = commit(ow, treeO, new ObjectId[] { B }); + final ObjectId P = commit(ow, treeP, new ObjectId[] { B }); + final ObjectId T = commit(ow, treeT, new ObjectId[] { P }); + + ThreeWayMerger twm = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db); + twm.setBase(P); + boolean merge = twm.merge(new ObjectId[] { O, T }); + assertTrue(merge); + + final TreeWalk tw = new TreeWalk(db); + tw.setRecursive(true); + tw.reset(twm.getResultTreeId()); + + assertTrue(tw.next()); + assertEquals("a", tw.getPathString()); + assertCorrectId(treeO, tw); + + assertTrue(tw.next()); + assertEquals("o", tw.getPathString()); + assertCorrectId(treeO, tw); + + assertTrue(tw.next()); + assertEquals("t", tw.getPathString()); + assertCorrectId(treeT, tw); + + assertFalse(tw.next()); + } + + private void assertCorrectId(final DirCache treeT, final TreeWalk tw) { + assertEquals(treeT.getEntry(tw.getPathString()).getObjectId(), tw + .getObjectId(0)); + } + + private ObjectId commit(final ObjectWriter ow, final DirCache treeB, + final ObjectId[] parentIds) throws Exception { + final Commit c = new Commit(db); + c.setTreeId(treeB.writeTree(ow)); + c.setAuthor(new PersonIdent("A U Thor", "a.u.thor", 1L, 0)); + c.setCommitter(c.getAuthor()); + c.setParentIds(parentIds); + c.setMessage("Tree " + c.getTreeId().name()); + return ow.writeCommit(c); + } + + private DirCacheEntry makeEntry(final String path, final FileMode mode) + throws Exception { + return makeEntry(path, mode, path); + } + + private DirCacheEntry makeEntry(final String path, final FileMode mode, + final String content) throws Exception { + final DirCacheEntry ent = new DirCacheEntry(path); + ent.setFileMode(mode); + final byte[] contentBytes = Constants.encode(content); + ent.setObjectId(new ObjectWriter(db).computeBlobSha1( + contentBytes.length, new ByteArrayInputStream(contentBytes))); + return ent; + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java new file mode 100644 index 000000000..0e5022006 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2008, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.ByteArrayInputStream; +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.lib.Commit; +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.lib.PersonIdent; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.treewalk.TreeWalk; + +public class SimpleMergeTest extends RepositoryTestCase { + + public void testOurs() throws IOException { + Merger ourMerger = MergeStrategy.OURS.newMerger(db); + boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("a"), db.resolve("c") }); + assertTrue(merge); + assertEquals(db.mapTree("a").getId(), ourMerger.getResultTreeId()); + } + + public void testTheirs() throws IOException { + Merger ourMerger = MergeStrategy.THEIRS.newMerger(db); + boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("a"), db.resolve("c") }); + assertTrue(merge); + assertEquals(db.mapTree("c").getId(), ourMerger.getResultTreeId()); + } + + public void testTrivialTwoWay() throws IOException { + Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db); + boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("a"), db.resolve("c") }); + assertTrue(merge); + assertEquals("02ba32d3649e510002c21651936b7077aa75ffa9",ourMerger.getResultTreeId().name()); + } + + public void testTrivialTwoWay_disjointhistories() throws IOException { + Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db); + boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("a"), db.resolve("c~4") }); + assertTrue(merge); + assertEquals("86265c33b19b2be71bdd7b8cb95823f2743d03a8",ourMerger.getResultTreeId().name()); + } + + public void testTrivialTwoWay_ok() throws IOException { + Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db); + boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("a^0^0^0"), db.resolve("a^0^0^1") }); + assertTrue(merge); + assertEquals(db.mapTree("a^0^0").getId(), ourMerger.getResultTreeId()); + } + + public void testTrivialTwoWay_conflict() throws IOException { + Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db); + boolean merge = ourMerger.merge(new ObjectId[] { db.resolve("f"), db.resolve("g") }); + assertFalse(merge); + } + + public void testTrivialTwoWay_validSubtreeSort() throws Exception { + final DirCache treeB = DirCache.read(db); + final DirCache treeO = DirCache.read(db); + final DirCache treeT = DirCache.read(db); + { + final DirCacheBuilder b = treeB.builder(); + final DirCacheBuilder o = treeO.builder(); + final DirCacheBuilder t = treeT.builder(); + + b.add(makeEntry("libelf-po/a", FileMode.REGULAR_FILE)); + b.add(makeEntry("libelf/c", FileMode.REGULAR_FILE)); + + o.add(makeEntry("Makefile", FileMode.REGULAR_FILE)); + o.add(makeEntry("libelf-po/a", FileMode.REGULAR_FILE)); + o.add(makeEntry("libelf/c", FileMode.REGULAR_FILE)); + + t.add(makeEntry("libelf-po/a", FileMode.REGULAR_FILE)); + t.add(makeEntry("libelf/c", FileMode.REGULAR_FILE, "blah")); + + b.finish(); + o.finish(); + t.finish(); + } + + final ObjectWriter ow = new ObjectWriter(db); + final ObjectId b = commit(ow, treeB, new ObjectId[] {}); + final ObjectId o = commit(ow, treeO, new ObjectId[] { b }); + final ObjectId t = commit(ow, treeT, new ObjectId[] { b }); + + Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db); + boolean merge = ourMerger.merge(new ObjectId[] { o, t }); + assertTrue(merge); + + final TreeWalk tw = new TreeWalk(db); + tw.setRecursive(true); + tw.reset(ourMerger.getResultTreeId()); + + assertTrue(tw.next()); + assertEquals("Makefile", tw.getPathString()); + assertCorrectId(treeO, tw); + + assertTrue(tw.next()); + assertEquals("libelf-po/a", tw.getPathString()); + assertCorrectId(treeO, tw); + + assertTrue(tw.next()); + assertEquals("libelf/c", tw.getPathString()); + assertCorrectId(treeT, tw); + + assertFalse(tw.next()); + } + + public void testTrivialTwoWay_concurrentSubtreeChange() throws Exception { + final DirCache treeB = DirCache.read(db); + final DirCache treeO = DirCache.read(db); + final DirCache treeT = DirCache.read(db); + { + final DirCacheBuilder b = treeB.builder(); + final DirCacheBuilder o = treeO.builder(); + final DirCacheBuilder t = treeT.builder(); + + b.add(makeEntry("d/o", FileMode.REGULAR_FILE)); + b.add(makeEntry("d/t", FileMode.REGULAR_FILE)); + + o.add(makeEntry("d/o", FileMode.REGULAR_FILE, "o !")); + o.add(makeEntry("d/t", FileMode.REGULAR_FILE)); + + t.add(makeEntry("d/o", FileMode.REGULAR_FILE)); + t.add(makeEntry("d/t", FileMode.REGULAR_FILE, "t !")); + + b.finish(); + o.finish(); + t.finish(); + } + + final ObjectWriter ow = new ObjectWriter(db); + final ObjectId b = commit(ow, treeB, new ObjectId[] {}); + final ObjectId o = commit(ow, treeO, new ObjectId[] { b }); + final ObjectId t = commit(ow, treeT, new ObjectId[] { b }); + + Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db); + boolean merge = ourMerger.merge(new ObjectId[] { o, t }); + assertTrue(merge); + + final TreeWalk tw = new TreeWalk(db); + tw.setRecursive(true); + tw.reset(ourMerger.getResultTreeId()); + + assertTrue(tw.next()); + assertEquals("d/o", tw.getPathString()); + assertCorrectId(treeO, tw); + + assertTrue(tw.next()); + assertEquals("d/t", tw.getPathString()); + assertCorrectId(treeT, tw); + + assertFalse(tw.next()); + } + + public void testTrivialTwoWay_conflictSubtreeChange() throws Exception { + final DirCache treeB = DirCache.read(db); + final DirCache treeO = DirCache.read(db); + final DirCache treeT = DirCache.read(db); + { + final DirCacheBuilder b = treeB.builder(); + final DirCacheBuilder o = treeO.builder(); + final DirCacheBuilder t = treeT.builder(); + + b.add(makeEntry("d/o", FileMode.REGULAR_FILE)); + b.add(makeEntry("d/t", FileMode.REGULAR_FILE)); + + o.add(makeEntry("d/o", FileMode.REGULAR_FILE)); + o.add(makeEntry("d/t", FileMode.REGULAR_FILE, "o !")); + + t.add(makeEntry("d/o", FileMode.REGULAR_FILE, "t !")); + t.add(makeEntry("d/t", FileMode.REGULAR_FILE, "t !")); + + b.finish(); + o.finish(); + t.finish(); + } + + final ObjectWriter ow = new ObjectWriter(db); + final ObjectId b = commit(ow, treeB, new ObjectId[] {}); + final ObjectId o = commit(ow, treeO, new ObjectId[] { b }); + final ObjectId t = commit(ow, treeT, new ObjectId[] { b }); + + Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db); + boolean merge = ourMerger.merge(new ObjectId[] { o, t }); + assertFalse(merge); + } + + public void testTrivialTwoWay_leftDFconflict1() throws Exception { + final DirCache treeB = DirCache.read(db); + final DirCache treeO = DirCache.read(db); + final DirCache treeT = DirCache.read(db); + { + final DirCacheBuilder b = treeB.builder(); + final DirCacheBuilder o = treeO.builder(); + final DirCacheBuilder t = treeT.builder(); + + b.add(makeEntry("d/o", FileMode.REGULAR_FILE)); + b.add(makeEntry("d/t", FileMode.REGULAR_FILE)); + + o.add(makeEntry("d", FileMode.REGULAR_FILE)); + + t.add(makeEntry("d/o", FileMode.REGULAR_FILE)); + t.add(makeEntry("d/t", FileMode.REGULAR_FILE, "t !")); + + b.finish(); + o.finish(); + t.finish(); + } + + final ObjectWriter ow = new ObjectWriter(db); + final ObjectId b = commit(ow, treeB, new ObjectId[] {}); + final ObjectId o = commit(ow, treeO, new ObjectId[] { b }); + final ObjectId t = commit(ow, treeT, new ObjectId[] { b }); + + Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db); + boolean merge = ourMerger.merge(new ObjectId[] { o, t }); + assertFalse(merge); + } + + public void testTrivialTwoWay_rightDFconflict1() throws Exception { + final DirCache treeB = DirCache.read(db); + final DirCache treeO = DirCache.read(db); + final DirCache treeT = DirCache.read(db); + { + final DirCacheBuilder b = treeB.builder(); + final DirCacheBuilder o = treeO.builder(); + final DirCacheBuilder t = treeT.builder(); + + b.add(makeEntry("d/o", FileMode.REGULAR_FILE)); + b.add(makeEntry("d/t", FileMode.REGULAR_FILE)); + + o.add(makeEntry("d/o", FileMode.REGULAR_FILE)); + o.add(makeEntry("d/t", FileMode.REGULAR_FILE, "o !")); + + t.add(makeEntry("d", FileMode.REGULAR_FILE)); + + b.finish(); + o.finish(); + t.finish(); + } + + final ObjectWriter ow = new ObjectWriter(db); + final ObjectId b = commit(ow, treeB, new ObjectId[] {}); + final ObjectId o = commit(ow, treeO, new ObjectId[] { b }); + final ObjectId t = commit(ow, treeT, new ObjectId[] { b }); + + Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db); + boolean merge = ourMerger.merge(new ObjectId[] { o, t }); + assertFalse(merge); + } + + public void testTrivialTwoWay_leftDFconflict2() throws Exception { + final DirCache treeB = DirCache.read(db); + final DirCache treeO = DirCache.read(db); + final DirCache treeT = DirCache.read(db); + { + final DirCacheBuilder b = treeB.builder(); + final DirCacheBuilder o = treeO.builder(); + final DirCacheBuilder t = treeT.builder(); + + b.add(makeEntry("d", FileMode.REGULAR_FILE)); + + o.add(makeEntry("d", FileMode.REGULAR_FILE, "o !")); + + t.add(makeEntry("d/o", FileMode.REGULAR_FILE)); + + b.finish(); + o.finish(); + t.finish(); + } + + final ObjectWriter ow = new ObjectWriter(db); + final ObjectId b = commit(ow, treeB, new ObjectId[] {}); + final ObjectId o = commit(ow, treeO, new ObjectId[] { b }); + final ObjectId t = commit(ow, treeT, new ObjectId[] { b }); + + Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db); + boolean merge = ourMerger.merge(new ObjectId[] { o, t }); + assertFalse(merge); + } + + public void testTrivialTwoWay_rightDFconflict2() throws Exception { + final DirCache treeB = DirCache.read(db); + final DirCache treeO = DirCache.read(db); + final DirCache treeT = DirCache.read(db); + { + final DirCacheBuilder b = treeB.builder(); + final DirCacheBuilder o = treeO.builder(); + final DirCacheBuilder t = treeT.builder(); + + b.add(makeEntry("d", FileMode.REGULAR_FILE)); + + o.add(makeEntry("d/o", FileMode.REGULAR_FILE)); + + t.add(makeEntry("d", FileMode.REGULAR_FILE, "t !")); + + b.finish(); + o.finish(); + t.finish(); + } + + final ObjectWriter ow = new ObjectWriter(db); + final ObjectId b = commit(ow, treeB, new ObjectId[] {}); + final ObjectId o = commit(ow, treeO, new ObjectId[] { b }); + final ObjectId t = commit(ow, treeT, new ObjectId[] { b }); + + Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db); + boolean merge = ourMerger.merge(new ObjectId[] { o, t }); + assertFalse(merge); + } + + private void assertCorrectId(final DirCache treeT, final TreeWalk tw) { + assertEquals(treeT.getEntry(tw.getPathString()).getObjectId(), tw + .getObjectId(0)); + } + + private ObjectId commit(final ObjectWriter ow, final DirCache treeB, + final ObjectId[] parentIds) throws Exception { + final Commit c = new Commit(db); + c.setTreeId(treeB.writeTree(ow)); + c.setAuthor(new PersonIdent("A U Thor", "a.u.thor", 1L, 0)); + c.setCommitter(c.getAuthor()); + c.setParentIds(parentIds); + c.setMessage("Tree " + c.getTreeId().name()); + return ow.writeCommit(c); + } + + private DirCacheEntry makeEntry(final String path, final FileMode mode) + throws Exception { + return makeEntry(path, mode, path); + } + + private DirCacheEntry makeEntry(final String path, final FileMode mode, + final String content) throws Exception { + final DirCacheEntry ent = new DirCacheEntry(path); + ent.setFileMode(mode); + final byte[] contentBytes = Constants.encode(content); + ent.setObjectId(new ObjectWriter(db).computeBlobSha1( + contentBytes.length, new ByteArrayInputStream(contentBytes))); + return ent; + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/EditListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/EditListTest.java new file mode 100644 index 000000000..c265bc097 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/EditListTest.java @@ -0,0 +1,101 @@ +/* + * 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.patch; + +import java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; + +import org.eclipse.jgit.diff.Edit; +import org.eclipse.jgit.diff.EditList; + +public class EditListTest extends TestCase { + public void testHunkHeader() throws IOException { + final Patch p = parseTestPatchFile("testGetText_BothISO88591.patch"); + final FileHeader fh = p.getFiles().get(0); + + final EditList list0 = fh.getHunks().get(0).toEditList(); + assertEquals(1, list0.size()); + assertEquals(new Edit(4 - 1, 5 - 1, 4 - 1, 5 - 1), list0.get(0)); + + final EditList list1 = fh.getHunks().get(1).toEditList(); + assertEquals(1, list1.size()); + assertEquals(new Edit(16 - 1, 17 - 1, 16 - 1, 17 - 1), list1.get(0)); + } + + public void testFileHeader() throws IOException { + final Patch p = parseTestPatchFile("testGetText_BothISO88591.patch"); + final FileHeader fh = p.getFiles().get(0); + final EditList e = fh.toEditList(); + assertEquals(2, e.size()); + assertEquals(new Edit(4 - 1, 5 - 1, 4 - 1, 5 - 1), e.get(0)); + assertEquals(new Edit(16 - 1, 17 - 1, 16 - 1, 17 - 1), e.get(1)); + } + + public void testTypes() throws IOException { + final Patch p = parseTestPatchFile("testEditList_Types.patch"); + final FileHeader fh = p.getFiles().get(0); + final EditList e = fh.toEditList(); + assertEquals(3, e.size()); + assertEquals(new Edit(3 - 1, 3 - 1, 3 - 1, 4 - 1), e.get(0)); + assertEquals(new Edit(17 - 1, 19 - 1, 18 - 1, 18 - 1), e.get(1)); + assertEquals(new Edit(23 - 1, 25 - 1, 22 - 1, 28 - 1), e.get(2)); + } + + private Patch parseTestPatchFile(final String patchFile) throws IOException { + final InputStream in = getClass().getResourceAsStream(patchFile); + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } + try { + final Patch p = new Patch(); + p.parse(in); + return p; + } finally { + in.close(); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/FileHeaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/FileHeaderTest.java new file mode 100644 index 000000000..8d9e302a8 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/FileHeaderTest.java @@ -0,0 +1,434 @@ +/* + * 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 junit.framework.TestCase; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; + +public class FileHeaderTest extends TestCase { + public void testParseGitFileName_Empty() { + final FileHeader fh = data(""); + assertEquals(-1, fh.parseGitFileName(0, fh.buf.length)); + assertNotNull(fh.getHunks()); + assertTrue(fh.getHunks().isEmpty()); + assertFalse(fh.hasMetaDataChanges()); + } + + public void testParseGitFileName_NoLF() { + final FileHeader fh = data("a/ b/"); + assertEquals(-1, fh.parseGitFileName(0, fh.buf.length)); + } + + public void testParseGitFileName_NoSecondLine() { + final FileHeader fh = data("\n"); + assertEquals(-1, fh.parseGitFileName(0, fh.buf.length)); + } + + public void testParseGitFileName_EmptyHeader() { + final FileHeader fh = data("\n\n"); + assertEquals(1, fh.parseGitFileName(0, fh.buf.length)); + } + + public void testParseGitFileName_Foo() { + final String name = "foo"; + final FileHeader fh = header(name); + assertEquals(gitLine(name).length(), fh.parseGitFileName(0, + fh.buf.length)); + assertEquals(name, fh.getOldName()); + assertSame(fh.getOldName(), fh.getNewName()); + assertFalse(fh.hasMetaDataChanges()); + } + + public void testParseGitFileName_FailFooBar() { + final FileHeader fh = data("a/foo b/bar\n-"); + assertTrue(fh.parseGitFileName(0, fh.buf.length) > 0); + assertNull(fh.getOldName()); + assertNull(fh.getNewName()); + assertFalse(fh.hasMetaDataChanges()); + } + + public void testParseGitFileName_FooSpBar() { + final String name = "foo bar"; + final FileHeader fh = header(name); + assertEquals(gitLine(name).length(), fh.parseGitFileName(0, + fh.buf.length)); + assertEquals(name, fh.getOldName()); + assertSame(fh.getOldName(), fh.getNewName()); + assertFalse(fh.hasMetaDataChanges()); + } + + public void testParseGitFileName_DqFooTabBar() { + final String name = "foo\tbar"; + final String dqName = "foo\\tbar"; + final FileHeader fh = dqHeader(dqName); + assertEquals(dqGitLine(dqName).length(), fh.parseGitFileName(0, + fh.buf.length)); + assertEquals(name, fh.getOldName()); + assertSame(fh.getOldName(), fh.getNewName()); + assertFalse(fh.hasMetaDataChanges()); + } + + public void testParseGitFileName_DqFooSpLfNulBar() { + final String name = "foo \n\0bar"; + final String dqName = "foo \\n\\0bar"; + final FileHeader fh = dqHeader(dqName); + assertEquals(dqGitLine(dqName).length(), fh.parseGitFileName(0, + fh.buf.length)); + assertEquals(name, fh.getOldName()); + assertSame(fh.getOldName(), fh.getNewName()); + assertFalse(fh.hasMetaDataChanges()); + } + + public void testParseGitFileName_SrcFooC() { + final String name = "src/foo/bar/argh/code.c"; + final FileHeader fh = header(name); + assertEquals(gitLine(name).length(), fh.parseGitFileName(0, + fh.buf.length)); + assertEquals(name, fh.getOldName()); + assertSame(fh.getOldName(), fh.getNewName()); + assertFalse(fh.hasMetaDataChanges()); + } + + public void testParseGitFileName_SrcFooCNonStandardPrefix() { + final String name = "src/foo/bar/argh/code.c"; + final String header = "project-v-1.0/" + name + " mydev/" + name + "\n"; + final FileHeader fh = data(header + "-"); + assertEquals(header.length(), fh.parseGitFileName(0, fh.buf.length)); + assertEquals(name, fh.getOldName()); + assertSame(fh.getOldName(), fh.getNewName()); + assertFalse(fh.hasMetaDataChanges()); + } + + public void testParseUnicodeName_NewFile() { + final FileHeader fh = data("diff --git \"a/\\303\\205ngstr\\303\\266m\" \"b/\\303\\205ngstr\\303\\266m\"\n" + + "new file mode 100644\n" + + "index 0000000..7898192\n" + + "--- /dev/null\n" + + "+++ \"b/\\303\\205ngstr\\303\\266m\"\n" + + "@@ -0,0 +1 @@\n" + "+a\n"); + assertParse(fh); + + assertEquals("/dev/null", fh.getOldName()); + assertSame(FileHeader.DEV_NULL, fh.getOldName()); + assertEquals("\u00c5ngstr\u00f6m", fh.getNewName()); + + assertSame(FileHeader.ChangeType.ADD, fh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); + assertTrue(fh.hasMetaDataChanges()); + + assertSame(FileMode.MISSING, fh.getOldMode()); + assertSame(FileMode.REGULAR_FILE, fh.getNewMode()); + + assertEquals("0000000", fh.getOldId().name()); + assertEquals("7898192", fh.getNewId().name()); + assertEquals(0, fh.getScore()); + } + + public void testParseUnicodeName_DeleteFile() { + final FileHeader fh = data("diff --git \"a/\\303\\205ngstr\\303\\266m\" \"b/\\303\\205ngstr\\303\\266m\"\n" + + "deleted file mode 100644\n" + + "index 7898192..0000000\n" + + "--- \"a/\\303\\205ngstr\\303\\266m\"\n" + + "+++ /dev/null\n" + + "@@ -1 +0,0 @@\n" + "-a\n"); + assertParse(fh); + + assertEquals("\u00c5ngstr\u00f6m", fh.getOldName()); + assertEquals("/dev/null", fh.getNewName()); + assertSame(FileHeader.DEV_NULL, fh.getNewName()); + + assertSame(FileHeader.ChangeType.DELETE, fh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); + assertTrue(fh.hasMetaDataChanges()); + + assertSame(FileMode.REGULAR_FILE, fh.getOldMode()); + assertSame(FileMode.MISSING, fh.getNewMode()); + + assertEquals("7898192", fh.getOldId().name()); + assertEquals("0000000", fh.getNewId().name()); + assertEquals(0, fh.getScore()); + } + + public void testParseModeChange() { + final FileHeader fh = data("diff --git a/a b b/a b\n" + + "old mode 100644\n" + "new mode 100755\n"); + assertParse(fh); + assertEquals("a b", fh.getOldName()); + assertEquals("a b", fh.getNewName()); + + assertSame(FileHeader.ChangeType.MODIFY, fh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); + assertTrue(fh.hasMetaDataChanges()); + + assertNull(fh.getOldId()); + assertNull(fh.getNewId()); + + assertSame(FileMode.REGULAR_FILE, fh.getOldMode()); + assertSame(FileMode.EXECUTABLE_FILE, fh.getNewMode()); + assertEquals(0, fh.getScore()); + } + + public void testParseRename100_NewStyle() { + final FileHeader fh = data("diff --git a/a b/ c/\\303\\205ngstr\\303\\266m\n" + + "similarity index 100%\n" + + "rename from a\n" + + "rename to \" c/\\303\\205ngstr\\303\\266m\"\n"); + int ptr = fh.parseGitFileName(0, fh.buf.length); + assertTrue(ptr > 0); + assertNull(fh.getOldName()); // can't parse names on a rename + assertNull(fh.getNewName()); + + ptr = fh.parseGitHeaders(ptr, fh.buf.length); + assertTrue(ptr > 0); + + assertEquals("a", fh.getOldName()); + assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewName()); + + assertSame(FileHeader.ChangeType.RENAME, fh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); + assertTrue(fh.hasMetaDataChanges()); + + assertNull(fh.getOldId()); + assertNull(fh.getNewId()); + + assertNull(fh.getOldMode()); + assertNull(fh.getNewMode()); + + assertEquals(100, fh.getScore()); + } + + public void testParseRename100_OldStyle() { + final FileHeader fh = data("diff --git a/a b/ c/\\303\\205ngstr\\303\\266m\n" + + "similarity index 100%\n" + + "rename old a\n" + + "rename new \" c/\\303\\205ngstr\\303\\266m\"\n"); + int ptr = fh.parseGitFileName(0, fh.buf.length); + assertTrue(ptr > 0); + assertNull(fh.getOldName()); // can't parse names on a rename + assertNull(fh.getNewName()); + + ptr = fh.parseGitHeaders(ptr, fh.buf.length); + assertTrue(ptr > 0); + + assertEquals("a", fh.getOldName()); + assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewName()); + + assertSame(FileHeader.ChangeType.RENAME, fh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); + assertTrue(fh.hasMetaDataChanges()); + + assertNull(fh.getOldId()); + assertNull(fh.getNewId()); + + assertNull(fh.getOldMode()); + assertNull(fh.getNewMode()); + + assertEquals(100, fh.getScore()); + } + + public void testParseCopy100() { + final FileHeader fh = data("diff --git a/a b/ c/\\303\\205ngstr\\303\\266m\n" + + "similarity index 100%\n" + + "copy from a\n" + + "copy to \" c/\\303\\205ngstr\\303\\266m\"\n"); + int ptr = fh.parseGitFileName(0, fh.buf.length); + assertTrue(ptr > 0); + assertNull(fh.getOldName()); // can't parse names on a copy + assertNull(fh.getNewName()); + + ptr = fh.parseGitHeaders(ptr, fh.buf.length); + assertTrue(ptr > 0); + + assertEquals("a", fh.getOldName()); + assertEquals(" c/\u00c5ngstr\u00f6m", fh.getNewName()); + + assertSame(FileHeader.ChangeType.COPY, fh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); + assertTrue(fh.hasMetaDataChanges()); + + assertNull(fh.getOldId()); + assertNull(fh.getNewId()); + + assertNull(fh.getOldMode()); + assertNull(fh.getNewMode()); + + assertEquals(100, fh.getScore()); + } + + public void testParseFullIndexLine_WithMode() { + final String oid = "78981922613b2afb6025042ff6bd878ac1994e85"; + final String nid = "61780798228d17af2d34fce4cfbdf35556832472"; + final FileHeader fh = data("diff --git a/a b/a\n" + "index " + oid + + ".." + nid + " 100644\n" + "--- a/a\n" + "+++ b/a\n"); + assertParse(fh); + + assertEquals("a", fh.getOldName()); + assertEquals("a", fh.getNewName()); + + assertSame(FileMode.REGULAR_FILE, fh.getOldMode()); + assertSame(FileMode.REGULAR_FILE, fh.getNewMode()); + assertFalse(fh.hasMetaDataChanges()); + + assertNotNull(fh.getOldId()); + assertNotNull(fh.getNewId()); + + assertTrue(fh.getOldId().isComplete()); + assertTrue(fh.getNewId().isComplete()); + + assertEquals(ObjectId.fromString(oid), fh.getOldId().toObjectId()); + assertEquals(ObjectId.fromString(nid), fh.getNewId().toObjectId()); + } + + public void testParseFullIndexLine_NoMode() { + final String oid = "78981922613b2afb6025042ff6bd878ac1994e85"; + final String nid = "61780798228d17af2d34fce4cfbdf35556832472"; + final FileHeader fh = data("diff --git a/a b/a\n" + "index " + oid + + ".." + nid + "\n" + "--- a/a\n" + "+++ b/a\n"); + assertParse(fh); + + assertEquals("a", fh.getOldName()); + assertEquals("a", fh.getNewName()); + assertFalse(fh.hasMetaDataChanges()); + + assertNull(fh.getOldMode()); + assertNull(fh.getNewMode()); + + assertNotNull(fh.getOldId()); + assertNotNull(fh.getNewId()); + + assertTrue(fh.getOldId().isComplete()); + assertTrue(fh.getNewId().isComplete()); + + assertEquals(ObjectId.fromString(oid), fh.getOldId().toObjectId()); + assertEquals(ObjectId.fromString(nid), fh.getNewId().toObjectId()); + } + + public void testParseAbbrIndexLine_WithMode() { + final int a = 7; + final String oid = "78981922613b2afb6025042ff6bd878ac1994e85"; + final String nid = "61780798228d17af2d34fce4cfbdf35556832472"; + final FileHeader fh = data("diff --git a/a b/a\n" + "index " + + oid.substring(0, a - 1) + ".." + nid.substring(0, a - 1) + + " 100644\n" + "--- a/a\n" + "+++ b/a\n"); + assertParse(fh); + + assertEquals("a", fh.getOldName()); + assertEquals("a", fh.getNewName()); + + assertSame(FileMode.REGULAR_FILE, fh.getOldMode()); + assertSame(FileMode.REGULAR_FILE, fh.getNewMode()); + assertFalse(fh.hasMetaDataChanges()); + + assertNotNull(fh.getOldId()); + assertNotNull(fh.getNewId()); + + assertFalse(fh.getOldId().isComplete()); + assertFalse(fh.getNewId().isComplete()); + + assertEquals(oid.substring(0, a - 1), fh.getOldId().name()); + assertEquals(nid.substring(0, a - 1), fh.getNewId().name()); + + assertTrue(ObjectId.fromString(oid).startsWith(fh.getOldId())); + assertTrue(ObjectId.fromString(nid).startsWith(fh.getNewId())); + } + + public void testParseAbbrIndexLine_NoMode() { + final int a = 7; + final String oid = "78981922613b2afb6025042ff6bd878ac1994e85"; + final String nid = "61780798228d17af2d34fce4cfbdf35556832472"; + final FileHeader fh = data("diff --git a/a b/a\n" + "index " + + oid.substring(0, a - 1) + ".." + nid.substring(0, a - 1) + + "\n" + "--- a/a\n" + "+++ b/a\n"); + assertParse(fh); + + assertEquals("a", fh.getOldName()); + assertEquals("a", fh.getNewName()); + + assertNull(fh.getOldMode()); + assertNull(fh.getNewMode()); + assertFalse(fh.hasMetaDataChanges()); + + assertNotNull(fh.getOldId()); + assertNotNull(fh.getNewId()); + + assertFalse(fh.getOldId().isComplete()); + assertFalse(fh.getNewId().isComplete()); + + assertEquals(oid.substring(0, a - 1), fh.getOldId().name()); + assertEquals(nid.substring(0, a - 1), fh.getNewId().name()); + + assertTrue(ObjectId.fromString(oid).startsWith(fh.getOldId())); + assertTrue(ObjectId.fromString(nid).startsWith(fh.getNewId())); + } + + private static void assertParse(final FileHeader fh) { + int ptr = fh.parseGitFileName(0, fh.buf.length); + assertTrue(ptr > 0); + ptr = fh.parseGitHeaders(ptr, fh.buf.length); + assertTrue(ptr > 0); + } + + private static FileHeader data(final String in) { + return new FileHeader(Constants.encodeASCII(in), 0); + } + + private static FileHeader header(final String path) { + return data(gitLine(path) + "--- " + path + "\n"); + } + + private static String gitLine(final String path) { + return "a/" + path + " b/" + path + "\n"; + } + + private static FileHeader dqHeader(final String path) { + return data(dqGitLine(path) + "--- " + path + "\n"); + } + + private static String dqGitLine(final String path) { + return "\"a/" + path + "\" \"b/" + path + "\"\n"; + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/GetTextTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/GetTextTest.java new file mode 100644 index 000000000..8d06987fd --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/GetTextTest.java @@ -0,0 +1,148 @@ +/* + * 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 java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; + +import junit.framework.TestCase; + +public class GetTextTest extends TestCase { + public void testGetText_BothISO88591() throws IOException { + final Charset cs = Charset.forName("ISO-8859-1"); + final Patch p = parseTestPatchFile(); + assertTrue(p.getErrors().isEmpty()); + assertEquals(1, p.getFiles().size()); + final FileHeader fh = p.getFiles().get(0); + assertEquals(2, fh.getHunks().size()); + assertEquals(readTestPatchFile(cs), fh.getScriptText(cs, cs)); + } + + public void testGetText_NoBinary() throws IOException { + final Charset cs = Charset.forName("ISO-8859-1"); + final Patch p = parseTestPatchFile(); + assertTrue(p.getErrors().isEmpty()); + assertEquals(1, p.getFiles().size()); + final FileHeader fh = p.getFiles().get(0); + assertEquals(0, fh.getHunks().size()); + assertEquals(readTestPatchFile(cs), fh.getScriptText(cs, cs)); + } + + public void testGetText_Convert() throws IOException { + final Charset csOld = Charset.forName("ISO-8859-1"); + final Charset csNew = Charset.forName("UTF-8"); + final Patch p = parseTestPatchFile(); + assertTrue(p.getErrors().isEmpty()); + assertEquals(1, p.getFiles().size()); + final FileHeader fh = p.getFiles().get(0); + assertEquals(2, fh.getHunks().size()); + + // Read the original file as ISO-8859-1 and fix up the one place + // where we changed the character encoding. That makes the exp + // string match what we really expect to get back. + // + String exp = readTestPatchFile(csOld); + exp = exp.replace("\303\205ngstr\303\266m", "\u00c5ngstr\u00f6m"); + + assertEquals(exp, fh.getScriptText(csOld, csNew)); + } + + public void testGetText_DiffCc() throws IOException { + final Charset csOld = Charset.forName("ISO-8859-1"); + final Charset csNew = Charset.forName("UTF-8"); + final Patch p = parseTestPatchFile(); + assertTrue(p.getErrors().isEmpty()); + assertEquals(1, p.getFiles().size()); + final CombinedFileHeader fh = (CombinedFileHeader) p.getFiles().get(0); + assertEquals(1, fh.getHunks().size()); + + // Read the original file as ISO-8859-1 and fix up the one place + // where we changed the character encoding. That makes the exp + // string match what we really expect to get back. + // + String exp = readTestPatchFile(csOld); + exp = exp.replace("\303\205ngstr\303\266m", "\u00c5ngstr\u00f6m"); + + assertEquals(exp, fh + .getScriptText(new Charset[] { csNew, csOld, csNew })); + } + + private Patch parseTestPatchFile() throws IOException { + final String patchFile = getName() + ".patch"; + final InputStream in = getClass().getResourceAsStream(patchFile); + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } + try { + final Patch p = new Patch(); + p.parse(in); + return p; + } finally { + in.close(); + } + } + + private String readTestPatchFile(final Charset cs) throws IOException { + final String patchFile = getName() + ".patch"; + final InputStream in = getClass().getResourceAsStream(patchFile); + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } + try { + final InputStreamReader r = new InputStreamReader(in, cs); + char[] tmp = new char[2048]; + final StringBuilder s = new StringBuilder(); + int n; + while ((n = r.read(tmp)) > 0) + s.append(tmp, 0, n); + return s.toString(); + } finally { + in.close(); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcErrorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcErrorTest.java new file mode 100644 index 000000000..f2bae6eb1 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcErrorTest.java @@ -0,0 +1,103 @@ +/* + * 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 java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; + +public class PatchCcErrorTest extends TestCase { + public void testError_CcTruncatedOld() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertEquals(3, p.getErrors().size()); + { + final FormatError e = p.getErrors().get(0); + assertSame(FormatError.Severity.ERROR, e.getSeverity()); + assertEquals( + "Truncated hunk, at least 1 lines is missing for ancestor 1", + e.getMessage()); + assertEquals(346, e.getOffset()); + assertTrue(e.getLineText().startsWith( + "@@@ -55,12 -163,13 +163,15 @@@ public ")); + } + { + final FormatError e = p.getErrors().get(1); + assertSame(FormatError.Severity.ERROR, e.getSeverity()); + assertEquals( + "Truncated hunk, at least 2 lines is missing for ancestor 2", + e.getMessage()); + assertEquals(346, e.getOffset()); + assertTrue(e.getLineText().startsWith( + "@@@ -55,12 -163,13 +163,15 @@@ public ")); + } + { + final FormatError e = p.getErrors().get(2); + assertSame(FormatError.Severity.ERROR, e.getSeverity()); + assertEquals("Truncated hunk, at least 3 new lines is missing", e + .getMessage()); + assertEquals(346, e.getOffset()); + assertTrue(e.getLineText().startsWith( + "@@@ -55,12 -163,13 +163,15 @@@ public ")); + } + } + + private Patch parseTestPatchFile() throws IOException { + final String patchFile = getName() + ".patch"; + final InputStream in = getClass().getResourceAsStream(patchFile); + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } + try { + final Patch p = new Patch(); + p.parse(in); + return p; + } finally { + in.close(); + } + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcTest.java new file mode 100644 index 000000000..e97d373d9 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchCcTest.java @@ -0,0 +1,206 @@ +/* + * 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 java.io.IOException; +import java.io.InputStream; + +import org.eclipse.jgit.lib.FileMode; + +import junit.framework.TestCase; + +public class PatchCcTest extends TestCase { + public void testParse_OneFileCc() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + final CombinedFileHeader cfh = (CombinedFileHeader) p.getFiles().get(0); + + assertEquals("org.spearce.egit.ui/src/org/spearce/egit/ui/UIText.java", + cfh.getNewName()); + assertEquals(cfh.getNewName(), cfh.getOldName()); + + assertEquals(98, cfh.startOffset); + + assertEquals(2, cfh.getParentCount()); + assertSame(cfh.getOldId(0), cfh.getOldId()); + assertEquals("169356b", cfh.getOldId(0).name()); + assertEquals("dd8c317", cfh.getOldId(1).name()); + assertEquals("fd85931", cfh.getNewId().name()); + + assertSame(cfh.getOldMode(0), cfh.getOldMode()); + assertSame(FileMode.REGULAR_FILE, cfh.getOldMode(0)); + assertSame(FileMode.REGULAR_FILE, cfh.getOldMode(1)); + assertSame(FileMode.EXECUTABLE_FILE, cfh.getNewMode()); + assertSame(FileHeader.ChangeType.MODIFY, cfh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, cfh.getPatchType()); + + assertEquals(1, cfh.getHunks().size()); + { + final CombinedHunkHeader h = cfh.getHunks().get(0); + + assertSame(cfh, h.getFileHeader()); + assertEquals(346, h.startOffset); + assertEquals(764, h.endOffset); + + assertSame(h.getOldImage(0), h.getOldImage()); + assertSame(cfh.getOldId(0), h.getOldImage(0).getId()); + assertSame(cfh.getOldId(1), h.getOldImage(1).getId()); + + assertEquals(55, h.getOldImage(0).getStartLine()); + assertEquals(12, h.getOldImage(0).getLineCount()); + assertEquals(3, h.getOldImage(0).getLinesAdded()); + assertEquals(0, h.getOldImage(0).getLinesDeleted()); + + assertEquals(163, h.getOldImage(1).getStartLine()); + assertEquals(13, h.getOldImage(1).getLineCount()); + assertEquals(2, h.getOldImage(1).getLinesAdded()); + assertEquals(0, h.getOldImage(1).getLinesDeleted()); + + assertEquals(163, h.getNewStartLine()); + assertEquals(15, h.getNewLineCount()); + + assertEquals(10, h.getLinesContext()); + } + } + + public void testParse_CcNewFile() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + final CombinedFileHeader cfh = (CombinedFileHeader) p.getFiles().get(0); + + assertSame(FileHeader.DEV_NULL, cfh.getOldName()); + assertEquals("d", cfh.getNewName()); + + assertEquals(187, cfh.startOffset); + + assertEquals(2, cfh.getParentCount()); + assertSame(cfh.getOldId(0), cfh.getOldId()); + assertEquals("0000000", cfh.getOldId(0).name()); + assertEquals("0000000", cfh.getOldId(1).name()); + assertEquals("4bcfe98", cfh.getNewId().name()); + + assertSame(cfh.getOldMode(0), cfh.getOldMode()); + assertSame(FileMode.MISSING, cfh.getOldMode(0)); + assertSame(FileMode.MISSING, cfh.getOldMode(1)); + assertSame(FileMode.REGULAR_FILE, cfh.getNewMode()); + assertSame(FileHeader.ChangeType.ADD, cfh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, cfh.getPatchType()); + + assertEquals(1, cfh.getHunks().size()); + { + final CombinedHunkHeader h = cfh.getHunks().get(0); + + assertSame(cfh, h.getFileHeader()); + assertEquals(273, h.startOffset); + assertEquals(300, h.endOffset); + + assertSame(h.getOldImage(0), h.getOldImage()); + assertSame(cfh.getOldId(0), h.getOldImage(0).getId()); + assertSame(cfh.getOldId(1), h.getOldImage(1).getId()); + + assertEquals(1, h.getOldImage(0).getStartLine()); + assertEquals(0, h.getOldImage(0).getLineCount()); + assertEquals(1, h.getOldImage(0).getLinesAdded()); + assertEquals(0, h.getOldImage(0).getLinesDeleted()); + + assertEquals(1, h.getOldImage(1).getStartLine()); + assertEquals(0, h.getOldImage(1).getLineCount()); + assertEquals(1, h.getOldImage(1).getLinesAdded()); + assertEquals(0, h.getOldImage(1).getLinesDeleted()); + + assertEquals(1, h.getNewStartLine()); + assertEquals(1, h.getNewLineCount()); + + assertEquals(0, h.getLinesContext()); + } + } + + public void testParse_CcDeleteFile() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + final CombinedFileHeader cfh = (CombinedFileHeader) p.getFiles().get(0); + + assertEquals("a", cfh.getOldName()); + assertSame(FileHeader.DEV_NULL, cfh.getNewName()); + + assertEquals(187, cfh.startOffset); + + assertEquals(2, cfh.getParentCount()); + assertSame(cfh.getOldId(0), cfh.getOldId()); + assertEquals("7898192", cfh.getOldId(0).name()); + assertEquals("2e65efe", cfh.getOldId(1).name()); + assertEquals("0000000", cfh.getNewId().name()); + + assertSame(cfh.getOldMode(0), cfh.getOldMode()); + assertSame(FileMode.REGULAR_FILE, cfh.getOldMode(0)); + assertSame(FileMode.REGULAR_FILE, cfh.getOldMode(1)); + assertSame(FileMode.MISSING, cfh.getNewMode()); + assertSame(FileHeader.ChangeType.DELETE, cfh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, cfh.getPatchType()); + + assertTrue(cfh.getHunks().isEmpty()); + } + + private Patch parseTestPatchFile() throws IOException { + final String patchFile = getName() + ".patch"; + final InputStream in = getClass().getResourceAsStream(patchFile); + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } + try { + final Patch p = new Patch(); + p.parse(in); + return p; + } finally { + in.close(); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchErrorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchErrorTest.java new file mode 100644 index 000000000..62a107130 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchErrorTest.java @@ -0,0 +1,180 @@ +/* + * 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 java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; + +public class PatchErrorTest extends TestCase { + public void testError_DisconnectedHunk() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + { + final FileHeader fh = p.getFiles().get(0); + assertEquals( + "org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java", + fh.getNewName()); + assertEquals(1, fh.getHunks().size()); + } + + assertEquals(1, p.getErrors().size()); + final FormatError e = p.getErrors().get(0); + assertSame(FormatError.Severity.ERROR, e.getSeverity()); + assertEquals("Hunk disconnected from file", e.getMessage()); + assertEquals(18, e.getOffset()); + assertTrue(e.getLineText().startsWith("@@ -109,4 +109,11 @@ assert")); + } + + public void testError_TruncatedOld() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertEquals(1, p.getErrors().size()); + + final FormatError e = p.getErrors().get(0); + assertSame(FormatError.Severity.ERROR, e.getSeverity()); + assertEquals("Truncated hunk, at least 1 old lines is missing", e + .getMessage()); + assertEquals(313, e.getOffset()); + assertTrue(e.getLineText().startsWith("@@ -236,9 +236,9 @@ protected ")); + } + + public void testError_TruncatedNew() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertEquals(1, p.getErrors().size()); + + final FormatError e = p.getErrors().get(0); + assertSame(FormatError.Severity.ERROR, e.getSeverity()); + assertEquals("Truncated hunk, at least 1 new lines is missing", e + .getMessage()); + assertEquals(313, e.getOffset()); + assertTrue(e.getLineText().startsWith("@@ -236,9 +236,9 @@ protected ")); + } + + public void testError_BodyTooLong() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertEquals(1, p.getErrors().size()); + + final FormatError e = p.getErrors().get(0); + assertSame(FormatError.Severity.WARNING, e.getSeverity()); + assertEquals("Hunk header 4:11 does not match body line count of 4:12", + e.getMessage()); + assertEquals(349, e.getOffset()); + assertTrue(e.getLineText().startsWith("@@ -109,4 +109,11 @@ assert")); + } + + public void testError_GarbageBetweenFiles() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(2, p.getFiles().size()); + { + final FileHeader fh = p.getFiles().get(0); + assertEquals( + "org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java", + fh.getNewName()); + assertEquals(1, fh.getHunks().size()); + } + { + final FileHeader fh = p.getFiles().get(1); + assertEquals( + "org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java", + fh.getNewName()); + assertEquals(1, fh.getHunks().size()); + } + + assertEquals(1, p.getErrors().size()); + final FormatError e = p.getErrors().get(0); + assertSame(FormatError.Severity.WARNING, e.getSeverity()); + assertEquals("Unexpected hunk trailer", e.getMessage()); + assertEquals(926, e.getOffset()); + assertEquals("I AM NOT HERE\n", e.getLineText()); + } + + public void testError_GitBinaryNoForwardHunk() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(2, p.getFiles().size()); + { + final FileHeader fh = p.getFiles().get(0); + assertEquals("org.spearce.egit.ui/icons/toolbar/fetchd.png", fh + .getNewName()); + assertSame(FileHeader.PatchType.GIT_BINARY, fh.getPatchType()); + assertTrue(fh.getHunks().isEmpty()); + assertNull(fh.getForwardBinaryHunk()); + } + { + final FileHeader fh = p.getFiles().get(1); + assertEquals("org.spearce.egit.ui/icons/toolbar/fetche.png", fh + .getNewName()); + assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); + assertTrue(fh.getHunks().isEmpty()); + assertNull(fh.getForwardBinaryHunk()); + } + + assertEquals(1, p.getErrors().size()); + final FormatError e = p.getErrors().get(0); + assertSame(FormatError.Severity.ERROR, e.getSeverity()); + assertEquals("Missing forward-image in GIT binary patch", e + .getMessage()); + assertEquals(297, e.getOffset()); + assertEquals("\n", e.getLineText()); + } + + private Patch parseTestPatchFile() throws IOException { + final String patchFile = getName() + ".patch"; + final InputStream in = getClass().getResourceAsStream(patchFile); + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } + try { + final Patch p = new Patch(); + p.parse(in); + return p; + } finally { + in.close(); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchTest.java new file mode 100644 index 000000000..52d6e27ca --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/patch/PatchTest.java @@ -0,0 +1,358 @@ +/* + * 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 java.io.IOException; +import java.io.InputStream; + +import junit.framework.TestCase; + +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; + +public class PatchTest extends TestCase { + public void testEmpty() { + final Patch p = new Patch(); + assertTrue(p.getFiles().isEmpty()); + assertTrue(p.getErrors().isEmpty()); + } + + public void testParse_ConfigCaseInsensitive() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(2, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + final FileHeader fRepositoryConfigTest = p.getFiles().get(0); + final FileHeader fRepositoryConfig = p.getFiles().get(1); + + assertEquals( + "org.eclipse.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java", + fRepositoryConfigTest.getNewName()); + + assertEquals( + "org.eclipse.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java", + fRepositoryConfig.getNewName()); + + assertEquals(572, fRepositoryConfigTest.startOffset); + assertEquals(1490, fRepositoryConfig.startOffset); + + assertEquals("da7e704", fRepositoryConfigTest.getOldId().name()); + assertEquals("34ce04a", fRepositoryConfigTest.getNewId().name()); + assertSame(FileHeader.PatchType.UNIFIED, fRepositoryConfigTest + .getPatchType()); + assertSame(FileMode.REGULAR_FILE, fRepositoryConfigTest.getOldMode()); + assertSame(FileMode.REGULAR_FILE, fRepositoryConfigTest.getNewMode()); + assertEquals(1, fRepositoryConfigTest.getHunks().size()); + { + final HunkHeader h = fRepositoryConfigTest.getHunks().get(0); + assertSame(fRepositoryConfigTest, h.getFileHeader()); + assertEquals(921, h.startOffset); + assertEquals(109, h.getOldImage().getStartLine()); + assertEquals(4, h.getOldImage().getLineCount()); + assertEquals(109, h.getNewStartLine()); + assertEquals(11, h.getNewLineCount()); + + assertEquals(4, h.getLinesContext()); + assertEquals(7, h.getOldImage().getLinesAdded()); + assertEquals(0, h.getOldImage().getLinesDeleted()); + assertSame(fRepositoryConfigTest.getOldId(), h.getOldImage() + .getId()); + + assertEquals(1490, h.endOffset); + } + + assertEquals("45c2f8a", fRepositoryConfig.getOldId().name()); + assertEquals("3291bba", fRepositoryConfig.getNewId().name()); + assertSame(FileHeader.PatchType.UNIFIED, fRepositoryConfig + .getPatchType()); + assertSame(FileMode.REGULAR_FILE, fRepositoryConfig.getOldMode()); + assertSame(FileMode.REGULAR_FILE, fRepositoryConfig.getNewMode()); + assertEquals(3, fRepositoryConfig.getHunks().size()); + { + final HunkHeader h = fRepositoryConfig.getHunks().get(0); + assertSame(fRepositoryConfig, h.getFileHeader()); + assertEquals(1803, h.startOffset); + assertEquals(236, h.getOldImage().getStartLine()); + assertEquals(9, h.getOldImage().getLineCount()); + assertEquals(236, h.getNewStartLine()); + assertEquals(9, h.getNewLineCount()); + + assertEquals(7, h.getLinesContext()); + assertEquals(2, h.getOldImage().getLinesAdded()); + assertEquals(2, h.getOldImage().getLinesDeleted()); + assertSame(fRepositoryConfig.getOldId(), h.getOldImage().getId()); + + assertEquals(2434, h.endOffset); + } + { + final HunkHeader h = fRepositoryConfig.getHunks().get(1); + assertEquals(2434, h.startOffset); + assertEquals(300, h.getOldImage().getStartLine()); + assertEquals(7, h.getOldImage().getLineCount()); + assertEquals(300, h.getNewStartLine()); + assertEquals(7, h.getNewLineCount()); + + assertEquals(6, h.getLinesContext()); + assertEquals(1, h.getOldImage().getLinesAdded()); + assertEquals(1, h.getOldImage().getLinesDeleted()); + + assertEquals(2816, h.endOffset); + } + { + final HunkHeader h = fRepositoryConfig.getHunks().get(2); + assertEquals(2816, h.startOffset); + assertEquals(954, h.getOldImage().getStartLine()); + assertEquals(7, h.getOldImage().getLineCount()); + assertEquals(954, h.getNewStartLine()); + assertEquals(7, h.getNewLineCount()); + + assertEquals(6, h.getLinesContext()); + assertEquals(1, h.getOldImage().getLinesAdded()); + assertEquals(1, h.getOldImage().getLinesDeleted()); + + assertEquals(3035, h.endOffset); + } + } + + public void testParse_NoBinary() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(5, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + for (int i = 0; i < 4; i++) { + final FileHeader fh = p.getFiles().get(i); + assertSame(FileHeader.ChangeType.ADD, fh.getChangeType()); + assertNotNull(fh.getOldId()); + assertNotNull(fh.getNewId()); + assertEquals("0000000", fh.getOldId().name()); + assertSame(FileMode.MISSING, fh.getOldMode()); + assertSame(FileMode.REGULAR_FILE, fh.getNewMode()); + assertTrue(fh.getNewName().startsWith( + "org.spearce.egit.ui/icons/toolbar/")); + assertSame(FileHeader.PatchType.BINARY, fh.getPatchType()); + assertTrue(fh.getHunks().isEmpty()); + assertTrue(fh.hasMetaDataChanges()); + + assertNull(fh.getForwardBinaryHunk()); + assertNull(fh.getReverseBinaryHunk()); + } + + final FileHeader fh = p.getFiles().get(4); + assertEquals("org.spearce.egit.ui/plugin.xml", fh.getNewName()); + assertSame(FileHeader.ChangeType.MODIFY, fh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); + assertFalse(fh.hasMetaDataChanges()); + assertEquals("ee8a5a0", fh.getNewId().name()); + assertNull(fh.getForwardBinaryHunk()); + assertNull(fh.getReverseBinaryHunk()); + assertEquals(1, fh.getHunks().size()); + assertEquals(272, fh.getHunks().get(0).getOldImage().getStartLine()); + } + + public void testParse_GitBinaryLiteral() throws IOException { + final Patch p = parseTestPatchFile(); + final int[] binsizes = { 359, 393, 372, 404 }; + assertEquals(5, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + for (int i = 0; i < 4; i++) { + final FileHeader fh = p.getFiles().get(i); + assertSame(FileHeader.ChangeType.ADD, fh.getChangeType()); + assertNotNull(fh.getOldId()); + assertNotNull(fh.getNewId()); + assertEquals(ObjectId.zeroId().name(), fh.getOldId().name()); + assertSame(FileMode.REGULAR_FILE, fh.getNewMode()); + assertTrue(fh.getNewName().startsWith( + "org.spearce.egit.ui/icons/toolbar/")); + assertSame(FileHeader.PatchType.GIT_BINARY, fh.getPatchType()); + assertTrue(fh.getHunks().isEmpty()); + assertTrue(fh.hasMetaDataChanges()); + + final BinaryHunk fwd = fh.getForwardBinaryHunk(); + final BinaryHunk rev = fh.getReverseBinaryHunk(); + assertNotNull(fwd); + assertNotNull(rev); + assertEquals(binsizes[i], fwd.getSize()); + assertEquals(0, rev.getSize()); + + assertSame(fh, fwd.getFileHeader()); + assertSame(fh, rev.getFileHeader()); + + assertSame(BinaryHunk.Type.LITERAL_DEFLATED, fwd.getType()); + assertSame(BinaryHunk.Type.LITERAL_DEFLATED, rev.getType()); + } + + final FileHeader fh = p.getFiles().get(4); + assertEquals("org.spearce.egit.ui/plugin.xml", fh.getNewName()); + assertSame(FileHeader.ChangeType.MODIFY, fh.getChangeType()); + assertSame(FileHeader.PatchType.UNIFIED, fh.getPatchType()); + assertFalse(fh.hasMetaDataChanges()); + assertEquals("ee8a5a0", fh.getNewId().name()); + assertNull(fh.getForwardBinaryHunk()); + assertNull(fh.getReverseBinaryHunk()); + assertEquals(1, fh.getHunks().size()); + assertEquals(272, fh.getHunks().get(0).getOldImage().getStartLine()); + } + + public void testParse_GitBinaryDelta() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + final FileHeader fh = p.getFiles().get(0); + assertTrue(fh.getNewName().startsWith("zero.bin")); + assertSame(FileHeader.ChangeType.MODIFY, fh.getChangeType()); + assertSame(FileHeader.PatchType.GIT_BINARY, fh.getPatchType()); + assertSame(FileMode.REGULAR_FILE, fh.getNewMode()); + + assertNotNull(fh.getOldId()); + assertNotNull(fh.getNewId()); + assertEquals("08e7df176454f3ee5eeda13efa0adaa54828dfd8", fh.getOldId() + .name()); + assertEquals("d70d8710b6d32ff844af0ee7c247e4b4b051867f", fh.getNewId() + .name()); + + assertTrue(fh.getHunks().isEmpty()); + assertFalse(fh.hasMetaDataChanges()); + + final BinaryHunk fwd = fh.getForwardBinaryHunk(); + final BinaryHunk rev = fh.getReverseBinaryHunk(); + assertNotNull(fwd); + assertNotNull(rev); + assertEquals(12, fwd.getSize()); + assertEquals(11, rev.getSize()); + + assertSame(fh, fwd.getFileHeader()); + assertSame(fh, rev.getFileHeader()); + + assertSame(BinaryHunk.Type.DELTA_DEFLATED, fwd.getType()); + assertSame(BinaryHunk.Type.DELTA_DEFLATED, rev.getType()); + + assertEquals(496, fh.endOffset); + } + + public void testParse_FixNoNewline() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + final FileHeader f = p.getFiles().get(0); + + assertEquals("a", f.getNewName()); + assertEquals(252, f.startOffset); + + assertEquals("2e65efe", f.getOldId().name()); + assertEquals("f2ad6c7", f.getNewId().name()); + assertSame(FileHeader.PatchType.UNIFIED, f.getPatchType()); + assertSame(FileMode.REGULAR_FILE, f.getOldMode()); + assertSame(FileMode.REGULAR_FILE, f.getNewMode()); + assertEquals(1, f.getHunks().size()); + { + final HunkHeader h = f.getHunks().get(0); + assertSame(f, h.getFileHeader()); + assertEquals(317, h.startOffset); + assertEquals(1, h.getOldImage().getStartLine()); + assertEquals(1, h.getOldImage().getLineCount()); + assertEquals(1, h.getNewStartLine()); + assertEquals(1, h.getNewLineCount()); + + assertEquals(0, h.getLinesContext()); + assertEquals(1, h.getOldImage().getLinesAdded()); + assertEquals(1, h.getOldImage().getLinesDeleted()); + assertSame(f.getOldId(), h.getOldImage().getId()); + + assertEquals(363, h.endOffset); + } + } + + public void testParse_AddNoNewline() throws IOException { + final Patch p = parseTestPatchFile(); + assertEquals(1, p.getFiles().size()); + assertTrue(p.getErrors().isEmpty()); + + final FileHeader f = p.getFiles().get(0); + + assertEquals("a", f.getNewName()); + assertEquals(256, f.startOffset); + + assertEquals("f2ad6c7", f.getOldId().name()); + assertEquals("c59d9b6", f.getNewId().name()); + assertSame(FileHeader.PatchType.UNIFIED, f.getPatchType()); + assertSame(FileMode.REGULAR_FILE, f.getOldMode()); + assertSame(FileMode.REGULAR_FILE, f.getNewMode()); + assertEquals(1, f.getHunks().size()); + { + final HunkHeader h = f.getHunks().get(0); + assertSame(f, h.getFileHeader()); + assertEquals(321, h.startOffset); + assertEquals(1, h.getOldImage().getStartLine()); + assertEquals(1, h.getOldImage().getLineCount()); + assertEquals(1, h.getNewStartLine()); + assertEquals(1, h.getNewLineCount()); + + assertEquals(0, h.getLinesContext()); + assertEquals(1, h.getOldImage().getLinesAdded()); + assertEquals(1, h.getOldImage().getLinesDeleted()); + assertSame(f.getOldId(), h.getOldImage().getId()); + + assertEquals(367, h.endOffset); + } + } + + private Patch parseTestPatchFile() throws IOException { + final String patchFile = getName() + ".patch"; + final InputStream in = getClass().getResourceAsStream(patchFile); + if (in == null) { + fail("No " + patchFile + " test vector"); + return null; // Never happens + } + try { + final Patch p = new Patch(); + p.parse(in); + return p; + } finally { + in.close(); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/AlwaysEmptyRevQueueTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/AlwaysEmptyRevQueueTest.java new file mode 100644 index 000000000..d752501c1 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/AlwaysEmptyRevQueueTest.java @@ -0,0 +1,69 @@ +/* + * 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; + +public class AlwaysEmptyRevQueueTest extends RevWalkTestCase { + private final AbstractRevQueue q = AbstractRevQueue.EMPTY_QUEUE; + + public void testEmpty() throws Exception { + assertNull(q.next()); + assertTrue(q.everbodyHasFlag(RevWalk.UNINTERESTING)); + assertFalse(q.anybodyHasFlag(RevWalk.UNINTERESTING)); + assertEquals(0, q.outputType()); + } + + public void testClear() throws Exception { + q.clear(); + testEmpty(); + } + + public void testAddFails() throws Exception { + try { + q.add(commit()); + fail("Did not throw UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // expected result + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/DateRevQueueTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/DateRevQueueTest.java new file mode 100644 index 000000000..b3a92951b --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/DateRevQueueTest.java @@ -0,0 +1,124 @@ +/* + * 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; + +public class DateRevQueueTest extends RevQueueTestCase { + protected DateRevQueue create() { + return new DateRevQueue(); + } + + public void testEmpty() throws Exception { + super.testEmpty(); + assertNull(q.peek()); + assertEquals(Generator.SORT_COMMIT_TIME_DESC, q.outputType()); + } + + public void testCloneEmpty() throws Exception { + q = new DateRevQueue(AbstractRevQueue.EMPTY_QUEUE); + assertNull(q.next()); + } + + public void testInsertOutOfOrder() throws Exception { + final RevCommit a = parse(commit()); + final RevCommit b = parse(commit(10, a)); + final RevCommit c1 = parse(commit(5, b)); + final RevCommit c2 = parse(commit(-50, b)); + + q.add(c2); + q.add(a); + q.add(b); + q.add(c1); + + assertCommit(c1, q.next()); + assertCommit(b, q.next()); + assertCommit(a, q.next()); + assertCommit(c2, q.next()); + assertNull(q.next()); + } + + public void testInsertTie() throws Exception { + final RevCommit a = parse(commit()); + final RevCommit b = parse(commit(0, a)); + { + q = create(); + q.add(a); + q.add(b); + + assertCommit(a, q.next()); + assertCommit(b, q.next()); + assertNull(q.next()); + } + { + q = create(); + q.add(b); + q.add(a); + + assertCommit(b, q.next()); + assertCommit(a, q.next()); + assertNull(q.next()); + } + } + + public void testCloneFIFO() throws Exception { + final RevCommit a = parse(commit()); + final RevCommit b = parse(commit(200, a)); + final RevCommit c = parse(commit(200, b)); + + final FIFORevQueue src = new FIFORevQueue(); + src.add(a); + src.add(b); + src.add(c); + + q = new DateRevQueue(src); + assertFalse(q.everbodyHasFlag(RevWalk.UNINTERESTING)); + assertFalse(q.anybodyHasFlag(RevWalk.UNINTERESTING)); + assertCommit(c, q.peek()); + assertCommit(c, q.peek()); + + assertCommit(c, q.next()); + assertCommit(b, q.next()); + assertCommit(a, q.next()); + assertNull(q.next()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FIFORevQueueTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FIFORevQueueTest.java new file mode 100644 index 000000000..3f4daab9f --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FIFORevQueueTest.java @@ -0,0 +1,87 @@ +/* + * 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.util.ArrayList; + +public class FIFORevQueueTest extends RevQueueTestCase { + protected FIFORevQueue create() { + return new FIFORevQueue(); + } + + public void testEmpty() throws Exception { + super.testEmpty(); + assertEquals(0, q.outputType()); + } + + public void testCloneEmpty() throws Exception { + q = new FIFORevQueue(AbstractRevQueue.EMPTY_QUEUE); + assertNull(q.next()); + } + + public void testAddLargeBlocks() throws Exception { + final ArrayList lst = new ArrayList(); + for (int i = 0; i < 3 * BlockRevQueue.Block.BLOCK_SIZE; i++) { + final RevCommit c = commit(); + lst.add(c); + q.add(c); + } + for (int i = 0; i < lst.size(); i++) + assertSame(lst.get(i), q.next()); + } + + public void testUnpopAtFront() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(); + final RevCommit c = commit(); + + q.add(a); + q.unpop(b); + q.unpop(c); + + assertSame(c, q.next()); + assertSame(b, q.next()); + assertSame(a, q.next()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FooterLineTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FooterLineTest.java new file mode 100644 index 000000000..d199f04cc --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/FooterLineTest.java @@ -0,0 +1,323 @@ +/* + * 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.util.List; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RepositoryTestCase; + +public class FooterLineTest extends RepositoryTestCase { + public void testNoFooters_EmptyBody() { + final RevCommit commit = parse(""); + final List footers = commit.getFooterLines(); + assertNotNull(footers); + assertEquals(0, footers.size()); + } + + public void testNoFooters_NewlineOnlyBody1() { + final RevCommit commit = parse("\n"); + final List footers = commit.getFooterLines(); + assertNotNull(footers); + assertEquals(0, footers.size()); + } + + public void testNoFooters_NewlineOnlyBody5() { + final RevCommit commit = parse("\n\n\n\n\n"); + final List footers = commit.getFooterLines(); + assertNotNull(footers); + assertEquals(0, footers.size()); + } + + public void testNoFooters_OneLineBodyNoLF() { + final RevCommit commit = parse("this is a commit"); + final List footers = commit.getFooterLines(); + assertNotNull(footers); + assertEquals(0, footers.size()); + } + + public void testNoFooters_OneLineBodyWithLF() { + final RevCommit commit = parse("this is a commit\n"); + final List footers = commit.getFooterLines(); + assertNotNull(footers); + assertEquals(0, footers.size()); + } + + public void testNoFooters_ShortBodyNoLF() { + final RevCommit commit = parse("subject\n\nbody of commit"); + final List footers = commit.getFooterLines(); + assertNotNull(footers); + assertEquals(0, footers.size()); + } + + public void testNoFooters_ShortBodyWithLF() { + final RevCommit commit = parse("subject\n\nbody of commit\n"); + final List footers = commit.getFooterLines(); + assertNotNull(footers); + assertEquals(0, footers.size()); + } + + public void testSignedOffBy_OneUserNoLF() { + final RevCommit commit = parse("subject\n\nbody of commit\n" + "\n" + + "Signed-off-by: A. U. Thor "); + final List footers = commit.getFooterLines(); + FooterLine f; + + assertNotNull(footers); + assertEquals(1, footers.size()); + + f = footers.get(0); + assertEquals("Signed-off-by", f.getKey()); + assertEquals("A. U. Thor ", f.getValue()); + assertEquals("a@example.com", f.getEmailAddress()); + } + + public void testSignedOffBy_OneUserWithLF() { + final RevCommit commit = parse("subject\n\nbody of commit\n" + "\n" + + "Signed-off-by: A. U. Thor \n"); + final List footers = commit.getFooterLines(); + FooterLine f; + + assertNotNull(footers); + assertEquals(1, footers.size()); + + f = footers.get(0); + assertEquals("Signed-off-by", f.getKey()); + assertEquals("A. U. Thor ", f.getValue()); + assertEquals("a@example.com", f.getEmailAddress()); + } + + public void testSignedOffBy_IgnoreWhitespace() { + // We only ignore leading whitespace on the value, trailing + // is assumed part of the value. + // + final RevCommit commit = parse("subject\n\nbody of commit\n" + "\n" + + "Signed-off-by: A. U. Thor \n"); + final List footers = commit.getFooterLines(); + FooterLine f; + + assertNotNull(footers); + assertEquals(1, footers.size()); + + f = footers.get(0); + assertEquals("Signed-off-by", f.getKey()); + assertEquals("A. U. Thor ", f.getValue()); + assertEquals("a@example.com", f.getEmailAddress()); + } + + public void testEmptyValueNoLF() { + final RevCommit commit = parse("subject\n\nbody of commit\n" + "\n" + + "Signed-off-by:"); + final List footers = commit.getFooterLines(); + FooterLine f; + + assertNotNull(footers); + assertEquals(1, footers.size()); + + f = footers.get(0); + assertEquals("Signed-off-by", f.getKey()); + assertEquals("", f.getValue()); + assertNull(f.getEmailAddress()); + } + + public void testEmptyValueWithLF() { + final RevCommit commit = parse("subject\n\nbody of commit\n" + "\n" + + "Signed-off-by:\n"); + final List footers = commit.getFooterLines(); + FooterLine f; + + assertNotNull(footers); + assertEquals(1, footers.size()); + + f = footers.get(0); + assertEquals("Signed-off-by", f.getKey()); + assertEquals("", f.getValue()); + assertNull(f.getEmailAddress()); + } + + public void testShortKey() { + final RevCommit commit = parse("subject\n\nbody of commit\n" + "\n" + + "K:V\n"); + final List footers = commit.getFooterLines(); + FooterLine f; + + assertNotNull(footers); + assertEquals(1, footers.size()); + + f = footers.get(0); + assertEquals("K", f.getKey()); + assertEquals("V", f.getValue()); + assertNull(f.getEmailAddress()); + } + + public void testNonDelimtedEmail() { + final RevCommit commit = parse("subject\n\nbody of commit\n" + "\n" + + "Acked-by: re@example.com\n"); + final List footers = commit.getFooterLines(); + FooterLine f; + + assertNotNull(footers); + assertEquals(1, footers.size()); + + f = footers.get(0); + assertEquals("Acked-by", f.getKey()); + assertEquals("re@example.com", f.getValue()); + assertEquals("re@example.com", f.getEmailAddress()); + } + + public void testNotEmail() { + final RevCommit commit = parse("subject\n\nbody of commit\n" + "\n" + + "Acked-by: Main Tain Er\n"); + final List footers = commit.getFooterLines(); + FooterLine f; + + assertNotNull(footers); + assertEquals(1, footers.size()); + + f = footers.get(0); + assertEquals("Acked-by", f.getKey()); + assertEquals("Main Tain Er", f.getValue()); + assertNull(f.getEmailAddress()); + } + + public void testSignedOffBy_ManyUsers() { + final RevCommit commit = parse("subject\n\nbody of commit\n" + + "Not-A-Footer-Line: this line must not be read as a footer\n" + + "\n" // paragraph break, now footers appear in final block + + "Signed-off-by: A. U. Thor \n" + + "CC: \n" + + "Acked-by: Some Reviewer \n" + + "Signed-off-by: Main Tain Er \n"); + final List footers = commit.getFooterLines(); + FooterLine f; + + assertNotNull(footers); + assertEquals(4, footers.size()); + + f = footers.get(0); + assertEquals("Signed-off-by", f.getKey()); + assertEquals("A. U. Thor ", f.getValue()); + assertEquals("a@example.com", f.getEmailAddress()); + + f = footers.get(1); + assertEquals("CC", f.getKey()); + assertEquals("", f.getValue()); + assertEquals("some.mailing.list@example.com", f.getEmailAddress()); + + f = footers.get(2); + assertEquals("Acked-by", f.getKey()); + assertEquals("Some Reviewer ", f.getValue()); + assertEquals("sr@example.com", f.getEmailAddress()); + + f = footers.get(3); + assertEquals("Signed-off-by", f.getKey()); + assertEquals("Main Tain Er ", f.getValue()); + assertEquals("mte@example.com", f.getEmailAddress()); + } + + public void testSignedOffBy_SkipNonFooter() { + final RevCommit commit = parse("subject\n\nbody of commit\n" + + "Not-A-Footer-Line: this line must not be read as a footer\n" + + "\n" // paragraph break, now footers appear in final block + + "Signed-off-by: A. U. Thor \n" + + "CC: \n" + + "not really a footer line but we'll skip it anyway\n" + + "Acked-by: Some Reviewer \n" + + "Signed-off-by: Main Tain Er \n"); + final List footers = commit.getFooterLines(); + FooterLine f; + + assertNotNull(footers); + assertEquals(4, footers.size()); + + f = footers.get(0); + assertEquals("Signed-off-by", f.getKey()); + assertEquals("A. U. Thor ", f.getValue()); + + f = footers.get(1); + assertEquals("CC", f.getKey()); + assertEquals("", f.getValue()); + + f = footers.get(2); + assertEquals("Acked-by", f.getKey()); + assertEquals("Some Reviewer ", f.getValue()); + + f = footers.get(3); + assertEquals("Signed-off-by", f.getKey()); + assertEquals("Main Tain Er ", f.getValue()); + } + + public void testFilterFootersIgnoreCase() { + final RevCommit commit = parse("subject\n\nbody of commit\n" + + "Not-A-Footer-Line: this line must not be read as a footer\n" + + "\n" // paragraph break, now footers appear in final block + + "Signed-Off-By: A. U. Thor \n" + + "CC: \n" + + "Acked-by: Some Reviewer \n" + + "signed-off-by: Main Tain Er \n"); + final List footers = commit.getFooterLines("signed-off-by"); + + assertNotNull(footers); + assertEquals(2, footers.size()); + + assertEquals("A. U. Thor ", footers.get(0)); + assertEquals("Main Tain Er ", footers.get(1)); + } + + private RevCommit parse(final String msg) { + final StringBuilder buf = new StringBuilder(); + buf.append("tree " + ObjectId.zeroId().name() + "\n"); + buf.append("author A. U. Thor 1 +0000\n"); + buf.append("committer A. U. Thor 1 +0000\n"); + buf.append("\n"); + buf.append(msg); + + final RevWalk walk = new RevWalk(db); + walk.setRetainBody(true); + final RevCommit c = new RevCommit(ObjectId.zeroId()); + c.parseCanonical(walk, Constants.encode(buf.toString())); + return c; + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/LIFORevQueueTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/LIFORevQueueTest.java new file mode 100644 index 000000000..7676a7150 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/LIFORevQueueTest.java @@ -0,0 +1,75 @@ +/* + * 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.util.ArrayList; +import java.util.Collections; + +public class LIFORevQueueTest extends RevQueueTestCase { + protected LIFORevQueue create() { + return new LIFORevQueue(); + } + + public void testEmpty() throws Exception { + super.testEmpty(); + assertEquals(0, q.outputType()); + } + + public void testCloneEmpty() throws Exception { + q = new LIFORevQueue(AbstractRevQueue.EMPTY_QUEUE); + assertNull(q.next()); + } + + public void testAddLargeBlocks() throws Exception { + final ArrayList lst = new ArrayList(); + for (int i = 0; i < 3 * BlockRevQueue.Block.BLOCK_SIZE; i++) { + final RevCommit c = commit(); + lst.add(c); + q.add(c); + } + Collections.reverse(lst); + for (int i = 0; i < lst.size(); i++) + assertSame(lst.get(i), q.next()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java new file mode 100644 index 000000000..7dddeee20 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java @@ -0,0 +1,196 @@ +/* + * 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; + +public class ObjectWalkTest extends RevWalkTestCase { + protected ObjectWalk objw; + + protected RevWalk createRevWalk() { + return objw = new ObjectWalk(db); + } + + public void testNoCommits() throws Exception { + assertNull(objw.next()); + assertNull(objw.nextObject()); + } + + public void testTwoCommitsEmptyTree() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + markStart(b); + + assertCommit(b, objw.next()); + assertCommit(a, objw.next()); + assertNull(objw.next()); + + assertSame(emptyTree, objw.nextObject()); + assertNull(objw.nextObject()); + } + + public void testOneCommitOneTreeTwoBlob() throws Exception { + final RevBlob f0 = blob("0"); + final RevBlob f1 = blob("1"); + final RevTree t = tree(file("0", f0), file("1", f1), file("2", f1)); + final RevCommit a = commit(t); + markStart(a); + + assertCommit(a, objw.next()); + assertNull(objw.next()); + + assertSame(t, objw.nextObject()); + assertSame(f0, objw.nextObject()); + assertSame(f1, objw.nextObject()); + assertNull(objw.nextObject()); + } + + public void testTwoCommitTwoTreeTwoBlob() throws Exception { + final RevBlob f0 = blob("0"); + final RevBlob f1 = blob("1"); + final RevBlob f2 = blob("0v2"); + final RevTree ta = tree(file("0", f0), file("1", f1), file("2", f1)); + final RevTree tb = tree(file("0", f2), file("1", f1), file("2", f1)); + final RevCommit a = commit(ta); + final RevCommit b = commit(tb, a); + markStart(b); + + assertCommit(b, objw.next()); + assertCommit(a, objw.next()); + assertNull(objw.next()); + + assertSame(tb, objw.nextObject()); + assertSame(f2, objw.nextObject()); + assertSame(f1, objw.nextObject()); + + assertSame(ta, objw.nextObject()); + assertSame(f0, objw.nextObject()); + + assertNull(objw.nextObject()); + } + + public void testTwoCommitDeepTree1() throws Exception { + final RevBlob f0 = blob("0"); + final RevBlob f1 = blob("0v2"); + final RevTree ta = tree(file("a/b/0", f0)); + final RevTree tb = tree(file("a/b/1", f1)); + final RevCommit a = commit(ta); + final RevCommit b = commit(tb, a); + markStart(b); + + assertCommit(b, objw.next()); + assertCommit(a, objw.next()); + assertNull(objw.next()); + + assertSame(tb, objw.nextObject()); + assertSame(get(tb, "a"), objw.nextObject()); + assertSame(get(tb, "a/b"), objw.nextObject()); + assertSame(f1, objw.nextObject()); + + assertSame(ta, objw.nextObject()); + assertSame(get(ta, "a"), objw.nextObject()); + assertSame(get(ta, "a/b"), objw.nextObject()); + assertSame(f0, objw.nextObject()); + + assertNull(objw.nextObject()); + } + + public void testTwoCommitDeepTree2() throws Exception { + final RevBlob f1 = blob("1"); + final RevTree ta = tree(file("a/b/0", f1), file("a/c/q", f1)); + final RevTree tb = tree(file("a/b/1", f1), file("a/c/q", f1)); + final RevCommit a = commit(ta); + final RevCommit b = commit(tb, a); + markStart(b); + + assertCommit(b, objw.next()); + assertCommit(a, objw.next()); + assertNull(objw.next()); + + assertSame(tb, objw.nextObject()); + assertSame(get(tb, "a"), objw.nextObject()); + assertSame(get(tb, "a/b"), objw.nextObject()); + assertSame(f1, objw.nextObject()); + assertSame(get(tb, "a/c"), objw.nextObject()); + + assertSame(ta, objw.nextObject()); + assertSame(get(ta, "a"), objw.nextObject()); + assertSame(get(ta, "a/b"), objw.nextObject()); + + assertNull(objw.nextObject()); + } + + public void testCull() throws Exception { + final RevBlob f1 = blob("1"); + final RevBlob f2 = blob("2"); + final RevBlob f3 = blob("3"); + final RevBlob f4 = blob("4"); + + final RevTree ta = tree(file("a/1", f1), file("c/3", f3)); + final RevCommit a = commit(ta); + + final RevTree tb = tree(file("a/1", f2), file("c/3", f3)); + final RevCommit b1 = commit(tb, a); + final RevCommit b2 = commit(tb, b1); + + final RevTree tc = tree(file("a/1", f4)); + final RevCommit c1 = commit(tc, a); + final RevCommit c2 = commit(tc, c1); + + markStart(b2); + markUninteresting(c2); + + assertCommit(b2, objw.next()); + assertCommit(b1, objw.next()); + assertNull(objw.next()); + + assertTrue(a.has(RevFlag.UNINTERESTING)); + assertTrue(ta.has(RevFlag.UNINTERESTING)); + assertTrue(f1.has(RevFlag.UNINTERESTING)); + assertTrue(f3.has(RevFlag.UNINTERESTING)); + + assertSame(tb, objw.nextObject()); + assertSame(get(tb, "a"), objw.nextObject()); + assertSame(f2, objw.nextObject()); + assertNull(objw.nextObject()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java new file mode 100644 index 000000000..b7e84419c --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevCommitParseTest.java @@ -0,0 +1,321 @@ +/* + * 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.revwalk; + +import java.io.ByteArrayOutputStream; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.RepositoryTestCase; + +public class RevCommitParseTest extends RepositoryTestCase { + public void testParse_NoParents() throws Exception { + final ObjectId treeId = id("9788669ad918b6fcce64af8882fc9a81cb6aba67"); + final String authorName = "A U. Thor"; + final String authorEmail = "a_u_thor@example.com"; + final int authorTime = 1218123387; + + final String committerName = "C O. Miter"; + final String committerEmail = "comiter@example.com"; + final int committerTime = 1218123390; + final StringBuilder body = new StringBuilder(); + + body.append("tree "); + body.append(treeId.name()); + body.append("\n"); + + body.append("author "); + body.append(authorName); + body.append(" <"); + body.append(authorEmail); + body.append("> "); + body.append(authorTime); + body.append(" +0700\n"); + + body.append("committer "); + body.append(committerName); + body.append(" <"); + body.append(committerEmail); + body.append("> "); + body.append(committerTime); + body.append(" -0500\n"); + + body.append("\n"); + + final RevWalk rw = new RevWalk(db); + final RevCommit c; + + c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + assertNull(c.getTree()); + assertNull(c.parents); + + c.parseCanonical(rw, body.toString().getBytes("UTF-8")); + assertNotNull(c.getTree()); + assertEquals(treeId, c.getTree().getId()); + assertSame(rw.lookupTree(treeId), c.getTree()); + + assertNotNull(c.parents); + assertEquals(0, c.parents.length); + assertEquals("", c.getFullMessage()); + + final PersonIdent cAuthor = c.getAuthorIdent(); + assertNotNull(cAuthor); + assertEquals(authorName, cAuthor.getName()); + assertEquals(authorEmail, cAuthor.getEmailAddress()); + + final PersonIdent cCommitter = c.getCommitterIdent(); + assertNotNull(cCommitter); + assertEquals(committerName, cCommitter.getName()); + assertEquals(committerEmail, cCommitter.getEmailAddress()); + } + + private RevCommit create(final String msg) throws Exception { + final StringBuilder b = new StringBuilder(); + b.append("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n"); + b.append("author A U. Thor 1218123387 +0700\n"); + b.append("committer C O. Miter 1218123390 -0500\n"); + b.append("\n"); + b.append(msg); + + final RevCommit c; + c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + c.parseCanonical(new RevWalk(db), b.toString().getBytes("UTF-8")); + return c; + } + + public void testParse_WeirdHeaderOnlyCommit() throws Exception { + final StringBuilder b = new StringBuilder(); + b.append("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n"); + b.append("author A U. Thor 1218123387 +0700\n"); + b.append("committer C O. Miter 1218123390 -0500\n"); + + final RevCommit c; + c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + c.parseCanonical(new RevWalk(db), b.toString().getBytes("UTF-8")); + + assertEquals("", c.getFullMessage()); + assertEquals("", c.getShortMessage()); + } + + public void testParse_implicit_UTF8_encoded() throws Exception { + final ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes("UTF-8")); + b.write("author F\u00f6r fattare 1218123387 +0700\n".getBytes("UTF-8")); + b.write("committer C O. Miter 1218123390 -0500\n".getBytes("UTF-8")); + b.write("\n".getBytes("UTF-8")); + b.write("Sm\u00f6rg\u00e5sbord\n".getBytes("UTF-8")); + b.write("\n".getBytes("UTF-8")); + b.write("\u304d\u308c\u3044\n".getBytes("UTF-8")); + final RevCommit c; + c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); // bogus id + c.parseCanonical(new RevWalk(db), b.toByteArray()); + + assertSame(Constants.CHARSET, c.getEncoding()); + assertEquals("F\u00f6r fattare", c.getAuthorIdent().getName()); + assertEquals("Sm\u00f6rg\u00e5sbord", c.getShortMessage()); + assertEquals("Sm\u00f6rg\u00e5sbord\n\n\u304d\u308c\u3044\n", c.getFullMessage()); + } + + public void testParse_implicit_mixed_encoded() throws Exception { + final ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes("UTF-8")); + b.write("author F\u00f6r fattare 1218123387 +0700\n".getBytes("ISO-8859-1")); + b.write("committer C O. Miter 1218123390 -0500\n".getBytes("UTF-8")); + b.write("\n".getBytes("UTF-8")); + b.write("Sm\u00f6rg\u00e5sbord\n".getBytes("UTF-8")); + b.write("\n".getBytes("UTF-8")); + b.write("\u304d\u308c\u3044\n".getBytes("UTF-8")); + final RevCommit c; + c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); // bogus id + c.parseCanonical(new RevWalk(db), b.toByteArray()); + + assertSame(Constants.CHARSET, c.getEncoding()); + assertEquals("F\u00f6r fattare", c.getAuthorIdent().getName()); + assertEquals("Sm\u00f6rg\u00e5sbord", c.getShortMessage()); + assertEquals("Sm\u00f6rg\u00e5sbord\n\n\u304d\u308c\u3044\n", c.getFullMessage()); + } + + /** + * Test parsing of a commit whose encoding is given and works. + * + * @throws Exception + */ + public void testParse_explicit_encoded() throws Exception { + final ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes("EUC-JP")); + b.write("author F\u00f6r fattare 1218123387 +0700\n".getBytes("EUC-JP")); + b.write("committer C O. Miter 1218123390 -0500\n".getBytes("EUC-JP")); + b.write("encoding euc_JP\n".getBytes("EUC-JP")); + b.write("\n".getBytes("EUC-JP")); + b.write("\u304d\u308c\u3044\n".getBytes("EUC-JP")); + b.write("\n".getBytes("EUC-JP")); + b.write("Hi\n".getBytes("EUC-JP")); + final RevCommit c; + c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); // bogus id + c.parseCanonical(new RevWalk(db), b.toByteArray()); + + assertEquals("EUC-JP", c.getEncoding().name()); + assertEquals("F\u00f6r fattare", c.getAuthorIdent().getName()); + assertEquals("\u304d\u308c\u3044", c.getShortMessage()); + assertEquals("\u304d\u308c\u3044\n\nHi\n", c.getFullMessage()); + } + + /** + * This is a twisted case, but show what we expect here. We can revise the + * expectations provided this case is updated. + * + * What happens here is that an encoding us given, but data is not encoded + * that way (and we can detect it), so we try other encodings. + * + * @throws Exception + */ + public void testParse_explicit_bad_encoded() throws Exception { + final ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes("UTF-8")); + b.write("author F\u00f6r fattare 1218123387 +0700\n".getBytes("ISO-8859-1")); + b.write("committer C O. Miter 1218123390 -0500\n".getBytes("UTF-8")); + b.write("encoding EUC-JP\n".getBytes("UTF-8")); + b.write("\n".getBytes("UTF-8")); + b.write("\u304d\u308c\u3044\n".getBytes("UTF-8")); + b.write("\n".getBytes("UTF-8")); + b.write("Hi\n".getBytes("UTF-8")); + final RevCommit c; + c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); // bogus id + c.parseCanonical(new RevWalk(db), b.toByteArray()); + + assertEquals("EUC-JP", c.getEncoding().name()); + assertEquals("F\u00f6r fattare", c.getAuthorIdent().getName()); + assertEquals("\u304d\u308c\u3044", c.getShortMessage()); + assertEquals("\u304d\u308c\u3044\n\nHi\n", c.getFullMessage()); + } + + /** + * This is a twisted case too, but show what we expect here. We can revise the + * expectations provided this case is updated. + * + * What happens here is that an encoding us given, but data is not encoded + * that way (and we can detect it), so we try other encodings. Here data could + * actually be decoded in the stated encoding, but we override using UTF-8. + * + * @throws Exception + */ + public void testParse_explicit_bad_encoded2() throws Exception { + final ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("tree 9788669ad918b6fcce64af8882fc9a81cb6aba67\n".getBytes("UTF-8")); + b.write("author F\u00f6r fattare 1218123387 +0700\n".getBytes("UTF-8")); + b.write("committer C O. Miter 1218123390 -0500\n".getBytes("UTF-8")); + b.write("encoding ISO-8859-1\n".getBytes("UTF-8")); + b.write("\n".getBytes("UTF-8")); + b.write("\u304d\u308c\u3044\n".getBytes("UTF-8")); + b.write("\n".getBytes("UTF-8")); + b.write("Hi\n".getBytes("UTF-8")); + final RevCommit c; + c = new RevCommit(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); // bogus id + c.parseCanonical(new RevWalk(db), b.toByteArray()); + + assertEquals("ISO-8859-1", c.getEncoding().name()); + assertEquals("F\u00f6r fattare", c.getAuthorIdent().getName()); + assertEquals("\u304d\u308c\u3044", c.getShortMessage()); + assertEquals("\u304d\u308c\u3044\n\nHi\n", c.getFullMessage()); + } + + public void testParse_NoMessage() throws Exception { + final String msg = ""; + final RevCommit c = create(msg); + assertEquals(msg, c.getFullMessage()); + assertEquals(msg, c.getShortMessage()); + } + + public void testParse_OnlyLFMessage() throws Exception { + final RevCommit c = create("\n"); + assertEquals("\n", c.getFullMessage()); + assertEquals("", c.getShortMessage()); + } + + public void testParse_ShortLineOnlyNoLF() throws Exception { + final String shortMsg = "This is a short message."; + final RevCommit c = create(shortMsg); + assertEquals(shortMsg, c.getFullMessage()); + assertEquals(shortMsg, c.getShortMessage()); + } + + public void testParse_ShortLineOnlyEndLF() throws Exception { + final String shortMsg = "This is a short message."; + final String fullMsg = shortMsg + "\n"; + final RevCommit c = create(fullMsg); + assertEquals(fullMsg, c.getFullMessage()); + assertEquals(shortMsg, c.getShortMessage()); + } + + public void testParse_ShortLineOnlyEmbeddedLF() throws Exception { + final String fullMsg = "This is a\nshort message."; + final String shortMsg = fullMsg.replace('\n', ' '); + final RevCommit c = create(fullMsg); + assertEquals(fullMsg, c.getFullMessage()); + assertEquals(shortMsg, c.getShortMessage()); + } + + public void testParse_ShortLineOnlyEmbeddedAndEndingLF() throws Exception { + final String fullMsg = "This is a\nshort message.\n"; + final String shortMsg = "This is a short message."; + final RevCommit c = create(fullMsg); + assertEquals(fullMsg, c.getFullMessage()); + assertEquals(shortMsg, c.getShortMessage()); + } + + public void testParse_GitStyleMessage() throws Exception { + final String shortMsg = "This fixes a bug."; + final String body = "We do it with magic and pixie dust and stuff.\n" + + "\n" + "Signed-off-by: A U. Thor \n"; + final String fullMsg = shortMsg + "\n" + "\n" + body; + final RevCommit c = create(fullMsg); + assertEquals(fullMsg, c.getFullMessage()); + assertEquals(shortMsg, c.getShortMessage()); + } + + private static ObjectId id(final String str) { + return ObjectId.fromString(str); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevFlagSetTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevFlagSetTest.java new file mode 100644 index 000000000..13f1cfc4c --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevFlagSetTest.java @@ -0,0 +1,137 @@ +/* + * 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.util.Arrays; +import java.util.Iterator; + +public class RevFlagSetTest extends RevWalkTestCase { + public void testEmpty() { + final RevFlagSet set = new RevFlagSet(); + assertEquals(0, set.mask); + assertEquals(0, set.size()); + assertNotNull(set.iterator()); + assertFalse(set.iterator().hasNext()); + } + + public void testAddOne() { + final String flagName = "flag"; + final RevFlag flag = rw.newFlag(flagName); + assertTrue(0 != flag.mask); + assertSame(flagName, flag.name); + + final RevFlagSet set = new RevFlagSet(); + assertTrue(set.add(flag)); + assertFalse(set.add(flag)); + assertEquals(flag.mask, set.mask); + assertEquals(1, set.size()); + final Iterator i = set.iterator(); + assertTrue(i.hasNext()); + assertSame(flag, i.next()); + assertFalse(i.hasNext()); + } + + public void testAddTwo() { + final RevFlag flag1 = rw.newFlag("flag_1"); + final RevFlag flag2 = rw.newFlag("flag_2"); + assertTrue((flag1.mask & flag2.mask) == 0); + + final RevFlagSet set = new RevFlagSet(); + assertTrue(set.add(flag1)); + assertTrue(set.add(flag2)); + assertEquals(flag1.mask | flag2.mask, set.mask); + assertEquals(2, set.size()); + } + + public void testContainsAll() { + final RevFlag flag1 = rw.newFlag("flag_1"); + final RevFlag flag2 = rw.newFlag("flag_2"); + final RevFlagSet set1 = new RevFlagSet(); + assertTrue(set1.add(flag1)); + assertTrue(set1.add(flag2)); + + assertTrue(set1.containsAll(set1)); + assertTrue(set1.containsAll(Arrays + .asList(new RevFlag[] { flag1, flag2 }))); + + final RevFlagSet set2 = new RevFlagSet(); + set2.add(rw.newFlag("flag_3")); + assertFalse(set1.containsAll(set2)); + } + + public void testEquals() { + final RevFlag flag1 = rw.newFlag("flag_1"); + final RevFlag flag2 = rw.newFlag("flag_2"); + final RevFlagSet set = new RevFlagSet(); + assertTrue(set.add(flag1)); + assertTrue(set.add(flag2)); + + assertTrue(new RevFlagSet(set).equals(set)); + assertTrue(new RevFlagSet(Arrays.asList(new RevFlag[] { flag1, flag2 })) + .equals(set)); + } + + public void testRemove() { + final RevFlag flag1 = rw.newFlag("flag_1"); + final RevFlag flag2 = rw.newFlag("flag_2"); + final RevFlagSet set = new RevFlagSet(); + assertTrue(set.add(flag1)); + assertTrue(set.add(flag2)); + + assertTrue(set.remove(flag1)); + assertFalse(set.remove(flag1)); + assertEquals(flag2.mask, set.mask); + assertFalse(set.contains(flag1)); + } + + public void testContains() { + final RevFlag flag1 = rw.newFlag("flag_1"); + final RevFlag flag2 = rw.newFlag("flag_2"); + final RevFlagSet set = new RevFlagSet(); + set.add(flag1); + assertTrue(set.contains(flag1)); + assertFalse(set.contains(flag2)); + assertFalse(set.contains("bob")); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevObjectTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevObjectTest.java new file mode 100644 index 000000000..87ecaa8c5 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevObjectTest.java @@ -0,0 +1,177 @@ +/* + * 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.AnyObjectId; +import org.eclipse.jgit.lib.Constants; + +public class RevObjectTest extends RevWalkTestCase { + public void testId() throws Exception { + final RevCommit a = commit(); + assertSame(a, a.getId()); + } + + public void testEqualsIsIdentity() throws Exception { + final RevCommit a1 = commit(); + final RevCommit b1 = commit(); + + assertTrue(a1.equals(a1)); + assertTrue(a1.equals((Object) a1)); + assertFalse(a1.equals(b1)); + + assertFalse(a1.equals(a1.copy())); + assertFalse(a1.equals((Object) a1.copy())); + assertFalse(a1.equals("")); + + final RevWalk rw2 = new RevWalk(db); + final RevCommit a2 = rw2.parseCommit(a1); + final RevCommit b2 = rw2.parseCommit(b1); + assertNotSame(a1, a2); + assertNotSame(b1, b2); + + assertFalse(a1.equals(a2)); + assertFalse(b1.equals(b2)); + + assertEquals(a1.hashCode(), a2.hashCode()); + assertEquals(b1.hashCode(), b2.hashCode()); + + assertTrue(AnyObjectId.equals(a1, a2)); + assertTrue(AnyObjectId.equals(b1, b2)); + } + + public void testRevObjectTypes() throws Exception { + assertEquals(Constants.OBJ_TREE, emptyTree.getType()); + assertEquals(Constants.OBJ_COMMIT, commit().getType()); + assertEquals(Constants.OBJ_BLOB, blob("").getType()); + assertEquals(Constants.OBJ_TAG, tag("emptyTree", emptyTree).getType()); + } + + public void testHasRevFlag() throws Exception { + final RevCommit a = commit(); + assertFalse(a.has(RevFlag.UNINTERESTING)); + a.flags |= RevWalk.UNINTERESTING; + assertTrue(a.has(RevFlag.UNINTERESTING)); + } + + public void testHasAnyFlag() throws Exception { + final RevCommit a = commit(); + final RevFlag flag1 = rw.newFlag("flag1"); + final RevFlag flag2 = rw.newFlag("flag2"); + final RevFlagSet s = new RevFlagSet(); + s.add(flag1); + s.add(flag2); + + assertFalse(a.hasAny(s)); + a.flags |= flag1.mask; + assertTrue(a.hasAny(s)); + } + + public void testHasAllFlag() throws Exception { + final RevCommit a = commit(); + final RevFlag flag1 = rw.newFlag("flag1"); + final RevFlag flag2 = rw.newFlag("flag2"); + final RevFlagSet s = new RevFlagSet(); + s.add(flag1); + s.add(flag2); + + assertFalse(a.hasAll(s)); + a.flags |= flag1.mask; + assertFalse(a.hasAll(s)); + a.flags |= flag2.mask; + assertTrue(a.hasAll(s)); + } + + public void testAddRevFlag() throws Exception { + final RevCommit a = commit(); + final RevFlag flag1 = rw.newFlag("flag1"); + final RevFlag flag2 = rw.newFlag("flag2"); + assertEquals(0, a.flags); + + a.add(flag1); + assertEquals(flag1.mask, a.flags); + + a.add(flag2); + assertEquals(flag1.mask | flag2.mask, a.flags); + } + + public void testAddRevFlagSet() throws Exception { + final RevCommit a = commit(); + final RevFlag flag1 = rw.newFlag("flag1"); + final RevFlag flag2 = rw.newFlag("flag2"); + final RevFlagSet s = new RevFlagSet(); + s.add(flag1); + s.add(flag2); + + assertEquals(0, a.flags); + + a.add(s); + assertEquals(flag1.mask | flag2.mask, a.flags); + } + + public void testRemoveRevFlag() throws Exception { + final RevCommit a = commit(); + final RevFlag flag1 = rw.newFlag("flag1"); + final RevFlag flag2 = rw.newFlag("flag2"); + a.add(flag1); + a.add(flag2); + assertEquals(flag1.mask | flag2.mask, a.flags); + a.remove(flag2); + assertEquals(flag1.mask, a.flags); + } + + public void testRemoveRevFlagSet() throws Exception { + final RevCommit a = commit(); + final RevFlag flag1 = rw.newFlag("flag1"); + final RevFlag flag2 = rw.newFlag("flag2"); + final RevFlag flag3 = rw.newFlag("flag3"); + final RevFlagSet s = new RevFlagSet(); + s.add(flag1); + s.add(flag2); + a.add(flag3); + a.add(s); + assertEquals(flag1.mask | flag2.mask | flag3.mask, a.flags); + a.remove(s); + assertEquals(flag3.mask, a.flags); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevQueueTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevQueueTestCase.java new file mode 100644 index 000000000..24e84b041 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevQueueTestCase.java @@ -0,0 +1,91 @@ +/* + * 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; + +public abstract class RevQueueTestCase extends + RevWalkTestCase { + protected T q; + + public void setUp() throws Exception { + super.setUp(); + q = create(); + } + + protected abstract T create(); + + public void testEmpty() throws Exception { + assertNull(q.next()); + assertTrue(q.everbodyHasFlag(RevWalk.UNINTERESTING)); + assertFalse(q.anybodyHasFlag(RevWalk.UNINTERESTING)); + } + + public void testClear() throws Exception { + final RevCommit a = parse(commit()); + final RevCommit b = parse(commit(a)); + + q.add(a); + q.add(b); + q.clear(); + assertNull(q.next()); + } + + public void testHasFlags() throws Exception { + final RevCommit a = parse(commit()); + final RevCommit b = parse(commit(a)); + + q.add(a); + q.add(b); + + assertFalse(q.everbodyHasFlag(RevWalk.UNINTERESTING)); + assertFalse(q.anybodyHasFlag(RevWalk.UNINTERESTING)); + + a.flags |= RevWalk.UNINTERESTING; + assertFalse(q.everbodyHasFlag(RevWalk.UNINTERESTING)); + assertTrue(q.anybodyHasFlag(RevWalk.UNINTERESTING)); + + b.flags |= RevWalk.UNINTERESTING; + assertTrue(q.everbodyHasFlag(RevWalk.UNINTERESTING)); + assertTrue(q.anybodyHasFlag(RevWalk.UNINTERESTING)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java new file mode 100644 index 000000000..8800536d2 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevTagParseTest.java @@ -0,0 +1,356 @@ +/* + * 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.revwalk; + +import java.io.ByteArrayOutputStream; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.RepositoryTestCase; + +public class RevTagParseTest extends RepositoryTestCase { + public void testTagBlob() throws Exception { + testOneType(Constants.OBJ_BLOB); + } + + public void testTagTree() throws Exception { + testOneType(Constants.OBJ_TREE); + } + + public void testTagCommit() throws Exception { + testOneType(Constants.OBJ_COMMIT); + } + + public void testTagTag() throws Exception { + testOneType(Constants.OBJ_TAG); + } + + private void testOneType(final int typeCode) throws Exception { + final ObjectId id = id("9788669ad918b6fcce64af8882fc9a81cb6aba67"); + final StringBuilder b = new StringBuilder(); + b.append("object " + id.name() + "\n"); + b.append("type " + Constants.typeString(typeCode) + "\n"); + b.append("tag v1.2.3.4.5\n"); + b.append("tagger A U. Thor 1218123387 +0700\n"); + b.append("\n"); + + final RevWalk rw = new RevWalk(db); + final RevTag c; + + c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + assertNull(c.getObject()); + assertNull(c.getTagName()); + + c.parseCanonical(rw, b.toString().getBytes("UTF-8")); + assertNotNull(c.getObject()); + assertEquals(id, c.getObject().getId()); + assertSame(rw.lookupAny(id, typeCode), c.getObject()); + } + + public void testParseAllFields() throws Exception { + final ObjectId treeId = id("9788669ad918b6fcce64af8882fc9a81cb6aba67"); + final String name = "v1.2.3.4.5"; + final String taggerName = "A U. Thor"; + final String taggerEmail = "a_u_thor@example.com"; + final int taggerTime = 1218123387; + + final StringBuilder body = new StringBuilder(); + + body.append("object "); + body.append(treeId.name()); + body.append("\n"); + + body.append("type tree\n"); + + body.append("tag "); + body.append(name); + body.append("\n"); + + body.append("tagger "); + body.append(taggerName); + body.append(" <"); + body.append(taggerEmail); + body.append("> "); + body.append(taggerTime); + body.append(" +0700\n"); + + body.append("\n"); + + final RevWalk rw = new RevWalk(db); + final RevTag c; + + c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + assertNull(c.getObject()); + assertNull(c.getTagName()); + + c.parseCanonical(rw, body.toString().getBytes("UTF-8")); + assertNotNull(c.getObject()); + assertEquals(treeId, c.getObject().getId()); + assertSame(rw.lookupTree(treeId), c.getObject()); + + assertNotNull(c.getTagName()); + assertEquals(name, c.getTagName()); + assertEquals("", c.getFullMessage()); + + final PersonIdent cTagger = c.getTaggerIdent(); + assertNotNull(cTagger); + assertEquals(taggerName, cTagger.getName()); + assertEquals(taggerEmail, cTagger.getEmailAddress()); + } + + private RevTag create(final String msg) throws Exception { + final StringBuilder b = new StringBuilder(); + b.append("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n"); + b.append("type tree\n"); + b.append("tag v1.2.3.4.5\n"); + b.append("tagger A U. Thor 1218123387 +0700\n"); + b.append("\n"); + b.append(msg); + + final RevTag c; + c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + c.parseCanonical(new RevWalk(db), b.toString().getBytes("UTF-8")); + return c; + } + + public void testParse_implicit_UTF8_encoded() throws Exception { + final ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n" + .getBytes("UTF-8")); + b.write("type tree\n".getBytes("UTF-8")); + b.write("tag v1.2.3.4.5\n".getBytes("UTF-8")); + + b + .write("tagger F\u00f6r fattare 1218123387 +0700\n" + .getBytes("UTF-8")); + b.write("\n".getBytes("UTF-8")); + b.write("Sm\u00f6rg\u00e5sbord\n".getBytes("UTF-8")); + b.write("\n".getBytes("UTF-8")); + b.write("\u304d\u308c\u3044\n".getBytes("UTF-8")); + final RevTag c; + c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + c.parseCanonical(new RevWalk(db), b.toByteArray()); + + assertEquals("F\u00f6r fattare", c.getTaggerIdent().getName()); + assertEquals("Sm\u00f6rg\u00e5sbord", c.getShortMessage()); + assertEquals("Sm\u00f6rg\u00e5sbord\n\n\u304d\u308c\u3044\n", c + .getFullMessage()); + } + + public void testParse_implicit_mixed_encoded() throws Exception { + final ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n" + .getBytes("UTF-8")); + b.write("type tree\n".getBytes("UTF-8")); + b.write("tag v1.2.3.4.5\n".getBytes("UTF-8")); + b + .write("tagger F\u00f6r fattare 1218123387 +0700\n" + .getBytes("ISO-8859-1")); + b.write("\n".getBytes("UTF-8")); + b.write("Sm\u00f6rg\u00e5sbord\n".getBytes("UTF-8")); + b.write("\n".getBytes("UTF-8")); + b.write("\u304d\u308c\u3044\n".getBytes("UTF-8")); + final RevTag c; + c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + c.parseCanonical(new RevWalk(db), b.toByteArray()); + + assertEquals("F\u00f6r fattare", c.getTaggerIdent().getName()); + assertEquals("Sm\u00f6rg\u00e5sbord", c.getShortMessage()); + assertEquals("Sm\u00f6rg\u00e5sbord\n\n\u304d\u308c\u3044\n", c + .getFullMessage()); + } + + /** + * Test parsing of a commit whose encoding is given and works. + * + * @throws Exception + */ + public void testParse_explicit_encoded() throws Exception { + final ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n" + .getBytes("EUC-JP")); + b.write("type tree\n".getBytes("EUC-JP")); + b.write("tag v1.2.3.4.5\n".getBytes("EUC-JP")); + b + .write("tagger F\u00f6r fattare 1218123387 +0700\n" + .getBytes("EUC-JP")); + b.write("encoding euc_JP\n".getBytes("EUC-JP")); + b.write("\n".getBytes("EUC-JP")); + b.write("\u304d\u308c\u3044\n".getBytes("EUC-JP")); + b.write("\n".getBytes("EUC-JP")); + b.write("Hi\n".getBytes("EUC-JP")); + final RevTag c; + c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + c.parseCanonical(new RevWalk(db), b.toByteArray()); + + assertEquals("F\u00f6r fattare", c.getTaggerIdent().getName()); + assertEquals("\u304d\u308c\u3044", c.getShortMessage()); + assertEquals("\u304d\u308c\u3044\n\nHi\n", c.getFullMessage()); + } + + /** + * This is a twisted case, but show what we expect here. We can revise the + * expectations provided this case is updated. + * + * What happens here is that an encoding us given, but data is not encoded + * that way (and we can detect it), so we try other encodings. + * + * @throws Exception + */ + public void testParse_explicit_bad_encoded() throws Exception { + final ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n" + .getBytes("UTF-8")); + b.write("type tree\n".getBytes("UTF-8")); + b.write("tag v1.2.3.4.5\n".getBytes("UTF-8")); + b + .write("tagger F\u00f6r fattare 1218123387 +0700\n" + .getBytes("ISO-8859-1")); + b.write("encoding EUC-JP\n".getBytes("UTF-8")); + b.write("\n".getBytes("UTF-8")); + b.write("\u304d\u308c\u3044\n".getBytes("UTF-8")); + b.write("\n".getBytes("UTF-8")); + b.write("Hi\n".getBytes("UTF-8")); + final RevTag c; + c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + c.parseCanonical(new RevWalk(db), b.toByteArray()); + + assertEquals("F\u00f6r fattare", c.getTaggerIdent().getName()); + assertEquals("\u304d\u308c\u3044", c.getShortMessage()); + assertEquals("\u304d\u308c\u3044\n\nHi\n", c.getFullMessage()); + } + + /** + * This is a twisted case too, but show what we expect here. We can revise + * the expectations provided this case is updated. + * + * What happens here is that an encoding us given, but data is not encoded + * that way (and we can detect it), so we try other encodings. Here data + * could actually be decoded in the stated encoding, but we override using + * UTF-8. + * + * @throws Exception + */ + public void testParse_explicit_bad_encoded2() throws Exception { + final ByteArrayOutputStream b = new ByteArrayOutputStream(); + b.write("object 9788669ad918b6fcce64af8882fc9a81cb6aba67\n" + .getBytes("UTF-8")); + b.write("type tree\n".getBytes("UTF-8")); + b.write("tag v1.2.3.4.5\n".getBytes("UTF-8")); + b + .write("tagger F\u00f6r fattare 1218123387 +0700\n" + .getBytes("UTF-8")); + b.write("encoding ISO-8859-1\n".getBytes("UTF-8")); + b.write("\n".getBytes("UTF-8")); + b.write("\u304d\u308c\u3044\n".getBytes("UTF-8")); + b.write("\n".getBytes("UTF-8")); + b.write("Hi\n".getBytes("UTF-8")); + final RevTag c; + c = new RevTag(id("9473095c4cb2f12aefe1db8a355fe3fafba42f67")); + c.parseCanonical(new RevWalk(db), b.toByteArray()); + + assertEquals("F\u00f6r fattare", c.getTaggerIdent().getName()); + assertEquals("\u304d\u308c\u3044", c.getShortMessage()); + assertEquals("\u304d\u308c\u3044\n\nHi\n", c.getFullMessage()); + } + + public void testParse_NoMessage() throws Exception { + final String msg = ""; + final RevTag c = create(msg); + assertEquals(msg, c.getFullMessage()); + assertEquals(msg, c.getShortMessage()); + } + + public void testParse_OnlyLFMessage() throws Exception { + final RevTag c = create("\n"); + assertEquals("\n", c.getFullMessage()); + assertEquals("", c.getShortMessage()); + } + + public void testParse_ShortLineOnlyNoLF() throws Exception { + final String shortMsg = "This is a short message."; + final RevTag c = create(shortMsg); + assertEquals(shortMsg, c.getFullMessage()); + assertEquals(shortMsg, c.getShortMessage()); + } + + public void testParse_ShortLineOnlyEndLF() throws Exception { + final String shortMsg = "This is a short message."; + final String fullMsg = shortMsg + "\n"; + final RevTag c = create(fullMsg); + assertEquals(fullMsg, c.getFullMessage()); + assertEquals(shortMsg, c.getShortMessage()); + } + + public void testParse_ShortLineOnlyEmbeddedLF() throws Exception { + final String fullMsg = "This is a\nshort message."; + final String shortMsg = fullMsg.replace('\n', ' '); + final RevTag c = create(fullMsg); + assertEquals(fullMsg, c.getFullMessage()); + assertEquals(shortMsg, c.getShortMessage()); + } + + public void testParse_ShortLineOnlyEmbeddedAndEndingLF() throws Exception { + final String fullMsg = "This is a\nshort message.\n"; + final String shortMsg = "This is a short message."; + final RevTag c = create(fullMsg); + assertEquals(fullMsg, c.getFullMessage()); + assertEquals(shortMsg, c.getShortMessage()); + } + + public void testParse_GitStyleMessage() throws Exception { + final String shortMsg = "This fixes a bug."; + final String body = "We do it with magic and pixie dust and stuff.\n" + + "\n" + "Signed-off-by: A U. Thor \n"; + final String fullMsg = shortMsg + "\n" + "\n" + body; + final RevTag c = create(fullMsg); + assertEquals(fullMsg, c.getFullMessage()); + assertEquals(shortMsg, c.getShortMessage()); + } + + private static ObjectId id(final String str) { + return ObjectId.fromString(str); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCullTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCullTest.java new file mode 100644 index 000000000..9e879c5f0 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkCullTest.java @@ -0,0 +1,100 @@ +/* + * 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; + +public class RevWalkCullTest extends RevWalkTestCase { + public void testProperlyCullAllAncestors1() throws Exception { + // Credit goes to Junio C Hamano for this + // test case in git-core (t/t6009-rev-list-parent.sh) + // + // We induce a clock skew so two is dated before one. + // + final RevCommit a = commit(); + final RevCommit b = commit(-2400, a); + final RevCommit c = commit(b); + final RevCommit d = commit(c); + + markStart(a); + markUninteresting(d); + assertNull(rw.next()); + } + + public void testProperlyCullAllAncestors2() throws Exception { + // Despite clock skew on c1 being very old it should not + // produce, neither should a or b, or any part of that chain. + // + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c1 = commit(-5, b); + final RevCommit c2 = commit(10, b); + final RevCommit d = commit(c1, c2); + + markStart(d); + markUninteresting(c1); + assertCommit(d, rw.next()); + assertCommit(c2, rw.next()); + assertNull(rw.next()); + } + + public void testProperlyCullAllAncestors_LongHistory() throws Exception { + final RevCommit a = commit(); + RevCommit b = commit(a); + for (int i = 0; i < 24; i++) { + b = commit(b); + if ((i & 2) == 0) + markUninteresting(b); + } + final RevCommit c = commit(b); + + markStart(c); + markUninteresting(b); + assertCommit(c, rw.next()); + assertNull(rw.next()); + + // We should have aborted before we got back so far that "a" + // would be parsed. Thus, its parents shouldn't be allocated. + // + assertNull(a.parents); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java new file mode 100644 index 000000000..db4c38e72 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkFilterTest.java @@ -0,0 +1,297 @@ +/* + * 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.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.filter.AndRevFilter; +import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter; +import org.eclipse.jgit.revwalk.filter.NotRevFilter; +import org.eclipse.jgit.revwalk.filter.OrRevFilter; +import org.eclipse.jgit.revwalk.filter.RevFilter; + +public class RevWalkFilterTest extends RevWalkTestCase { + private static final MyAll MY_ALL = new MyAll(); + + public void testFilter_ALL() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(b); + + rw.setRevFilter(RevFilter.ALL); + markStart(c); + assertCommit(c, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + public void testFilter_Negate_ALL() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(b); + + rw.setRevFilter(RevFilter.ALL.negate()); + markStart(c); + assertNull(rw.next()); + } + + public void testFilter_NOT_ALL() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(b); + + rw.setRevFilter(NotRevFilter.create(RevFilter.ALL)); + markStart(c); + assertNull(rw.next()); + } + + public void testFilter_NONE() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(b); + + rw.setRevFilter(RevFilter.NONE); + markStart(c); + assertNull(rw.next()); + } + + public void testFilter_NOT_NONE() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(b); + + rw.setRevFilter(NotRevFilter.create(RevFilter.NONE)); + markStart(c); + assertCommit(c, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + public void testFilter_ALL_And_NONE() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(b); + + rw.setRevFilter(AndRevFilter.create(RevFilter.ALL, RevFilter.NONE)); + markStart(c); + assertNull(rw.next()); + } + + public void testFilter_NONE_And_ALL() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(b); + + rw.setRevFilter(AndRevFilter.create(RevFilter.NONE, RevFilter.ALL)); + markStart(c); + assertNull(rw.next()); + } + + public void testFilter_ALL_Or_NONE() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(b); + + rw.setRevFilter(OrRevFilter.create(RevFilter.ALL, RevFilter.NONE)); + markStart(c); + assertCommit(c, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + public void testFilter_NONE_Or_ALL() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(b); + + rw.setRevFilter(OrRevFilter.create(RevFilter.NONE, RevFilter.ALL)); + markStart(c); + assertCommit(c, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + public void testFilter_MY_ALL_And_NONE() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(b); + + rw.setRevFilter(AndRevFilter.create(MY_ALL, RevFilter.NONE)); + markStart(c); + assertNull(rw.next()); + } + + public void testFilter_NONE_And_MY_ALL() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(b); + + rw.setRevFilter(AndRevFilter.create(RevFilter.NONE, MY_ALL)); + markStart(c); + assertNull(rw.next()); + } + + public void testFilter_MY_ALL_Or_NONE() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(b); + + rw.setRevFilter(OrRevFilter.create(MY_ALL, RevFilter.NONE)); + markStart(c); + assertCommit(c, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + public void testFilter_NONE_Or_MY_ALL() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(b); + + rw.setRevFilter(OrRevFilter.create(RevFilter.NONE, MY_ALL)); + markStart(c); + assertCommit(c, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + public void testFilter_NO_MERGES() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c1 = commit(b); + final RevCommit c2 = commit(b); + final RevCommit d = commit(c1, c2); + final RevCommit e = commit(d); + + rw.setRevFilter(RevFilter.NO_MERGES); + markStart(e); + assertCommit(e, rw.next()); + assertCommit(c2, rw.next()); + assertCommit(c1, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + public void testCommitTimeRevFilter() throws Exception { + final RevCommit a = commit(); + tick(100); + + final RevCommit b = commit(a); + tick(100); + + Date since = new Date(nowTick); + final RevCommit c1 = commit(b); + tick(100); + + final RevCommit c2 = commit(b); + tick(100); + + Date until = new Date(nowTick); + final RevCommit d = commit(c1, c2); + tick(100); + + final RevCommit e = commit(d); + + { + RevFilter after = CommitTimeRevFilter.after(since); + assertNotNull(after); + rw.setRevFilter(after); + markStart(e); + assertCommit(e, rw.next()); + assertCommit(d, rw.next()); + assertCommit(c2, rw.next()); + assertCommit(c1, rw.next()); + assertNull(rw.next()); + } + + { + RevFilter before = CommitTimeRevFilter.before(until); + assertNotNull(before); + rw.reset(); + rw.setRevFilter(before); + markStart(e); + assertCommit(c2, rw.next()); + assertCommit(c1, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + { + RevFilter between = CommitTimeRevFilter.between(since, until); + assertNotNull(between); + rw.reset(); + rw.setRevFilter(between); + markStart(e); + assertCommit(c2, rw.next()); + assertCommit(c1, rw.next()); + assertNull(rw.next()); + } + } + + private static class MyAll extends RevFilter { + @Override + public RevFilter clone() { + return this; + } + + @Override + public boolean include(RevWalk walker, RevCommit cmit) + throws StopWalkException, MissingObjectException, + IncorrectObjectTypeException, IOException { + return true; + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.java new file mode 100644 index 000000000..10c9f9b12 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkMergeBaseTest.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.revwalk; + +import org.eclipse.jgit.revwalk.filter.RevFilter; +import org.eclipse.jgit.treewalk.filter.TreeFilter; + +public class RevWalkMergeBaseTest extends RevWalkTestCase { + public void testNone() throws Exception { + final RevCommit c1 = commit(commit(commit())); + final RevCommit c2 = commit(commit(commit())); + + rw.setRevFilter(RevFilter.MERGE_BASE); + markStart(c1); + markStart(c2); + assertNull(rw.next()); + } + + public void testDisallowTreeFilter() throws Exception { + final RevCommit c1 = commit(); + final RevCommit c2 = commit(); + + rw.setRevFilter(RevFilter.MERGE_BASE); + rw.setTreeFilter(TreeFilter.ANY_DIFF); + markStart(c1); + markStart(c2); + try { + assertNull(rw.next()); + fail("did not throw IllegalStateException"); + } catch (IllegalStateException ise) { + // expected result + } + } + + public void testSimple() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c1 = commit(commit(commit(commit(commit(b))))); + final RevCommit c2 = commit(commit(commit(commit(commit(b))))); + + rw.setRevFilter(RevFilter.MERGE_BASE); + markStart(c1); + markStart(c2); + assertCommit(b, rw.next()); + assertNull(rw.next()); + } + + public void testMultipleHeads_SameBase1() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c1 = commit(commit(commit(commit(commit(b))))); + final RevCommit c2 = commit(commit(commit(commit(commit(b))))); + final RevCommit c3 = commit(commit(commit(b))); + + rw.setRevFilter(RevFilter.MERGE_BASE); + markStart(c1); + markStart(c2); + markStart(c3); + assertCommit(b, rw.next()); + assertNull(rw.next()); + } + + public void testMultipleHeads_SameBase2() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(b); + final RevCommit d1 = commit(commit(commit(commit(commit(b))))); + final RevCommit d2 = commit(commit(commit(commit(commit(c))))); + final RevCommit d3 = commit(commit(commit(c))); + + rw.setRevFilter(RevFilter.MERGE_BASE); + markStart(d1); + markStart(d2); + markStart(d3); + assertCommit(b, rw.next()); + assertNull(rw.next()); + } + + public void testCrissCross() throws Exception { + // See http://marc.info/?l=git&m=111463358500362&w=2 for a nice + // description of what this test is creating. We don't have a + // clean merge base for d,e as they each merged the parents b,c + // in different orders. + // + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(a); + final RevCommit d = commit(b, c); + final RevCommit e = commit(c, b); + + rw.setRevFilter(RevFilter.MERGE_BASE); + markStart(d); + markStart(e); + assertCommit(c, rw.next()); + assertCommit(b, rw.next()); + assertNull(rw.next()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter1Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter1Test.java new file mode 100644 index 000000000..986a88656 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter1Test.java @@ -0,0 +1,182 @@ +/* + * 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.util.Collections; + +import org.eclipse.jgit.treewalk.filter.AndTreeFilter; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; +import org.eclipse.jgit.treewalk.filter.TreeFilter; + +public class RevWalkPathFilter1Test extends RevWalkTestCase { + protected void filter(final String path) { + rw.setTreeFilter(AndTreeFilter.create(PathFilterGroup + .createFromStrings(Collections.singleton(path)), + TreeFilter.ANY_DIFF)); + } + + public void testEmpty_EmptyTree() throws Exception { + final RevCommit a = commit(); + filter("a"); + markStart(a); + assertNull(rw.next()); + } + + public void testEmpty_NoMatch() throws Exception { + final RevCommit a = commit(tree(file("0", blob("0")))); + filter("a"); + markStart(a); + assertNull(rw.next()); + } + + public void testSimple1() throws Exception { + final RevCommit a = commit(tree(file("0", blob("0")))); + filter("0"); + markStart(a); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + public void testEdits_MatchNone() throws Exception { + final RevCommit a = commit(tree(file("0", blob("a")))); + final RevCommit b = commit(tree(file("0", blob("b"))), a); + final RevCommit c = commit(tree(file("0", blob("c"))), b); + final RevCommit d = commit(tree(file("0", blob("d"))), c); + filter("a"); + markStart(d); + assertNull(rw.next()); + } + + public void testEdits_MatchAll() throws Exception { + final RevCommit a = commit(tree(file("0", blob("a")))); + final RevCommit b = commit(tree(file("0", blob("b"))), a); + final RevCommit c = commit(tree(file("0", blob("c"))), b); + final RevCommit d = commit(tree(file("0", blob("d"))), c); + filter("0"); + markStart(d); + assertCommit(d, rw.next()); + assertCommit(c, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + public void testStringOfPearls_FilePath1() throws Exception { + final RevCommit a = commit(tree(file("d/f", blob("a")))); + final RevCommit b = commit(tree(file("d/f", blob("a"))), a); + final RevCommit c = commit(tree(file("d/f", blob("b"))), b); + filter("d/f"); + markStart(c); + + assertCommit(c, rw.next()); + assertEquals(1, c.getParentCount()); + assertCommit(a, c.getParent(0)); // b was skipped + + assertCommit(a, rw.next()); + assertEquals(0, a.getParentCount()); + assertNull(rw.next()); + } + + public void testStringOfPearls_FilePath2() throws Exception { + final RevCommit a = commit(tree(file("d/f", blob("a")))); + final RevCommit b = commit(tree(file("d/f", blob("a"))), a); + final RevCommit c = commit(tree(file("d/f", blob("b"))), b); + final RevCommit d = commit(tree(file("d/f", blob("b"))), c); + filter("d/f"); + markStart(d); + + // d was skipped + assertCommit(c, rw.next()); + assertEquals(1, c.getParentCount()); + assertCommit(a, c.getParent(0)); // b was skipped + + assertCommit(a, rw.next()); + assertEquals(0, a.getParentCount()); + assertNull(rw.next()); + } + + public void testStringOfPearls_DirPath2() throws Exception { + final RevCommit a = commit(tree(file("d/f", blob("a")))); + final RevCommit b = commit(tree(file("d/f", blob("a"))), a); + final RevCommit c = commit(tree(file("d/f", blob("b"))), b); + final RevCommit d = commit(tree(file("d/f", blob("b"))), c); + filter("d"); + markStart(d); + + // d was skipped + assertCommit(c, rw.next()); + assertEquals(1, c.getParentCount()); + assertCommit(a, c.getParent(0)); // b was skipped + + assertCommit(a, rw.next()); + assertEquals(0, a.getParentCount()); + assertNull(rw.next()); + } + + public void testStringOfPearls_FilePath3() throws Exception { + final RevCommit a = commit(tree(file("d/f", blob("a")))); + final RevCommit b = commit(tree(file("d/f", blob("a"))), a); + final RevCommit c = commit(tree(file("d/f", blob("b"))), b); + final RevCommit d = commit(tree(file("d/f", blob("b"))), c); + final RevCommit e = commit(tree(file("d/f", blob("b"))), d); + final RevCommit f = commit(tree(file("d/f", blob("b"))), e); + final RevCommit g = commit(tree(file("d/f", blob("b"))), f); + final RevCommit h = commit(tree(file("d/f", blob("b"))), g); + final RevCommit i = commit(tree(file("d/f", blob("c"))), h); + filter("d/f"); + markStart(i); + + assertCommit(i, rw.next()); + assertEquals(1, i.getParentCount()); + assertCommit(c, i.getParent(0)); // h..d was skipped + + assertCommit(c, rw.next()); + assertEquals(1, c.getParentCount()); + assertCommit(a, c.getParent(0)); // b was skipped + + assertCommit(a, rw.next()); + assertEquals(0, a.getParentCount()); + assertNull(rw.next()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter6012Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter6012Test.java new file mode 100644 index 000000000..73d41eae6 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkPathFilter6012Test.java @@ -0,0 +1,168 @@ +/* + * 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.lang.reflect.Field; +import java.util.Collections; +import java.util.HashMap; + +import org.eclipse.jgit.treewalk.filter.AndTreeFilter; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; +import org.eclipse.jgit.treewalk.filter.TreeFilter; + +// Note: Much of this test case is broken as it depends upon +// the graph applying topological sorting *before* doing merge +// simplification. It also depends upon a difference between +// full history and non-full history for a path, something we +// don't quite yet have a distiction for in JGit. +// +public class RevWalkPathFilter6012Test extends RevWalkTestCase { + private static final String pA = "pA", pF = "pF", pE = "pE"; + + private RevCommit a, b, c, d, e, f, g, h, i; + + private HashMap byName; + + public void setUp() throws Exception { + super.setUp(); + + // Test graph was stolen from git-core t6012-rev-list-simplify + // (by Junio C Hamano in 65347030590bcc251a9ff2ed96487a0f1b9e9fa8) + // + final RevBlob zF = blob("zF"); + final RevBlob zH = blob("zH"); + final RevBlob zI = blob("zI"); + final RevBlob zS = blob("zS"); + final RevBlob zY = blob("zY"); + + a = commit(tree(file(pF, zH))); + b = commit(tree(file(pF, zI)), a); + c = commit(tree(file(pF, zI)), a); + d = commit(tree(file(pA, zS), file(pF, zI)), c); + parse(d); + e = commit(d.getTree(), d, b); + f = commit(tree(file(pA, zS), file(pE, zY), file(pF, zI)), e); + parse(f); + g = commit(tree(file(pE, zY), file(pF, zI)), b); + h = commit(f.getTree(), g, f); + i = commit(tree(file(pA, zS), file(pE, zY), file(pF, zF)), h); + + byName = new HashMap(); + for (Field z : RevWalkPathFilter6012Test.class.getDeclaredFields()) { + if (z.getType() == RevCommit.class) + byName.put((RevCommit) z.get(this), z.getName()); + } + } + + protected void check(final RevCommit... order) throws Exception { + markStart(i); + final StringBuilder act = new StringBuilder(); + for (final RevCommit z : rw) { + final String name = byName.get(z); + assertNotNull(name); + act.append(name); + act.append(' '); + } + final StringBuilder exp = new StringBuilder(); + for (final RevCommit z : order) { + final String name = byName.get(z); + assertNotNull(name); + exp.append(name); + exp.append(' '); + } + assertEquals(exp.toString(), act.toString()); + } + + protected void filter(final String path) { + rw.setTreeFilter(AndTreeFilter.create(PathFilterGroup + .createFromStrings(Collections.singleton(path)), + TreeFilter.ANY_DIFF)); + } + + public void test1() throws Exception { + // TODO --full-history + check(i, h, g, f, e, d, c, b, a); + } + + public void test2() throws Exception { + // TODO --full-history + filter(pF); + // TODO fix broken test + // check(i, h, e, c, b, a); + } + + public void test3() throws Exception { + // TODO --full-history + rw.sort(RevSort.TOPO); + filter(pF); + // TODO fix broken test + // check(i, h, e, c, b, a); + } + + public void test4() throws Exception { + // TODO --full-history + rw.sort(RevSort.COMMIT_TIME_DESC); + filter(pF); + // TODO fix broken test + // check(i, h, e, c, b, a); + } + + public void test5() throws Exception { + // TODO --simplify-merges + filter(pF); + // TODO fix broken test + // check(i, e, c, b, a); + } + + public void test6() throws Exception { + filter(pF); + check(i, b, a); + } + + public void test7() throws Exception { + rw.sort(RevSort.TOPO); + filter(pF); + check(i, b, a); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkSortTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkSortTest.java new file mode 100644 index 000000000..0d3e0cf5a --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkSortTest.java @@ -0,0 +1,168 @@ +/* + * 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; + +public class RevWalkSortTest extends RevWalkTestCase { + public void testSort_Default() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(1, a); + final RevCommit c = commit(1, b); + final RevCommit d = commit(1, c); + + markStart(d); + assertCommit(d, rw.next()); + assertCommit(c, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + public void testSort_COMMIT_TIME_DESC() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(b); + final RevCommit d = commit(c); + + rw.sort(RevSort.COMMIT_TIME_DESC); + markStart(d); + assertCommit(d, rw.next()); + assertCommit(c, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + public void testSort_REVERSE() throws Exception { + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(b); + final RevCommit d = commit(c); + + rw.sort(RevSort.REVERSE); + markStart(d); + assertCommit(a, rw.next()); + assertCommit(b, rw.next()); + assertCommit(c, rw.next()); + assertCommit(d, rw.next()); + assertNull(rw.next()); + } + + public void testSort_COMMIT_TIME_DESC_OutOfOrder1() throws Exception { + // Despite being out of order time-wise, a strand-of-pearls must + // still maintain topological order. + // + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c = commit(-5, b); + final RevCommit d = commit(10, c); + assertTrue(parse(a).getCommitTime() < parse(d).getCommitTime()); + assertTrue(parse(c).getCommitTime() < parse(b).getCommitTime()); + + rw.sort(RevSort.COMMIT_TIME_DESC); + markStart(d); + assertCommit(d, rw.next()); + assertCommit(c, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + public void testSort_COMMIT_TIME_DESC_OutOfOrder2() throws Exception { + // c1 is back dated before its parent. + // + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c1 = commit(-5, b); + final RevCommit c2 = commit(10, b); + final RevCommit d = commit(c1, c2); + + rw.sort(RevSort.COMMIT_TIME_DESC); + markStart(d); + assertCommit(d, rw.next()); + assertCommit(c2, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertCommit(c1, rw.next()); + assertNull(rw.next()); + } + + public void testSort_TOPO() throws Exception { + // c1 is back dated before its parent. + // + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c1 = commit(-5, b); + final RevCommit c2 = commit(10, b); + final RevCommit d = commit(c1, c2); + + rw.sort(RevSort.TOPO); + markStart(d); + assertCommit(d, rw.next()); + assertCommit(c2, rw.next()); + assertCommit(c1, rw.next()); + assertCommit(b, rw.next()); + assertCommit(a, rw.next()); + assertNull(rw.next()); + } + + public void testSort_TOPO_REVERSE() throws Exception { + // c1 is back dated before its parent. + // + final RevCommit a = commit(); + final RevCommit b = commit(a); + final RevCommit c1 = commit(-5, b); + final RevCommit c2 = commit(10, b); + final RevCommit d = commit(c1, c2); + + rw.sort(RevSort.TOPO); + rw.sort(RevSort.REVERSE, true); + markStart(d); + assertCommit(a, rw.next()); + assertCommit(b, rw.next()); + assertCommit(c1, rw.next()); + assertCommit(c2, rw.next()); + assertCommit(d, rw.next()); + assertNull(rw.next()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java new file mode 100644 index 000000000..50fbce41a --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/RevWalkTestCase.java @@ -0,0 +1,183 @@ +/* + * 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.util.Collections; +import java.util.Date; + +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.lib.Commit; +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.lib.PersonIdent; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.lib.Tag; +import org.eclipse.jgit.lib.Tree; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; + +/** Support for tests of the {@link RevWalk} class. */ +public abstract class RevWalkTestCase extends RepositoryTestCase { + protected ObjectWriter ow; + + protected RevTree emptyTree; + + protected long nowTick; + + protected RevWalk rw; + + public void setUp() throws Exception { + super.setUp(); + ow = new ObjectWriter(db); + rw = createRevWalk(); + emptyTree = rw.parseTree(ow.writeTree(new Tree(db))); + nowTick = 1236977987000L; + } + + protected RevWalk createRevWalk() { + return new RevWalk(db); + } + + protected void tick(final int secDelta) { + nowTick += secDelta * 1000L; + } + + protected RevBlob blob(final String content) throws Exception { + return rw.lookupBlob(ow.writeBlob(Constants.encode(content))); + } + + protected DirCacheEntry file(final String path, final RevBlob blob) + throws Exception { + final DirCacheEntry e = new DirCacheEntry(path); + e.setFileMode(FileMode.REGULAR_FILE); + e.setObjectId(blob); + return e; + } + + protected RevTree tree(final DirCacheEntry... entries) throws Exception { + final DirCache dc = DirCache.newInCore(); + final DirCacheBuilder b = dc.builder(); + for (final DirCacheEntry e : entries) + b.add(e); + b.finish(); + return rw.lookupTree(dc.writeTree(ow)); + } + + protected RevObject get(final RevTree tree, final String path) + throws Exception { + final TreeWalk tw = new TreeWalk(db); + tw.setFilter(PathFilterGroup.createFromStrings(Collections + .singleton(path))); + tw.reset(tree); + while (tw.next()) { + if (tw.isSubtree() && !path.equals(tw.getPathString())) { + tw.enterSubtree(); + continue; + } + final ObjectId entid = tw.getObjectId(0); + final FileMode entmode = tw.getFileMode(0); + return rw.lookupAny(entid, entmode.getObjectType()); + } + fail("Can't find " + path + " in tree " + tree.name()); + return null; // never reached. + } + + protected RevCommit commit(final RevCommit... parents) throws Exception { + return commit(1, emptyTree, parents); + } + + protected RevCommit commit(final RevTree tree, final RevCommit... parents) + throws Exception { + return commit(1, tree, parents); + } + + protected RevCommit commit(final int secDelta, final RevCommit... parents) + throws Exception { + return commit(secDelta, emptyTree, parents); + } + + protected RevCommit commit(final int secDelta, final RevTree tree, + final RevCommit... parents) throws Exception { + tick(secDelta); + final Commit c = new Commit(db); + c.setTreeId(tree); + c.setParentIds(parents); + c.setAuthor(new PersonIdent(jauthor, new Date(nowTick))); + c.setCommitter(new PersonIdent(jcommitter, new Date(nowTick))); + c.setMessage(""); + return rw.lookupCommit(ow.writeCommit(c)); + } + + protected RevTag tag(final String name, final RevObject dst) + throws Exception { + final Tag t = new Tag(db); + t.setType(Constants.typeString(dst.getType())); + t.setObjId(dst.toObjectId()); + t.setTag(name); + t.setTagger(new PersonIdent(jcommitter, new Date(nowTick))); + t.setMessage(""); + return (RevTag) rw.lookupAny(ow.writeTag(t), Constants.OBJ_TAG); + } + + protected T parse(final T t) throws Exception { + rw.parseBody(t); + return t; + } + + protected void markStart(final RevCommit commit) throws Exception { + rw.markStart(commit); + } + + protected void markUninteresting(final RevCommit commit) throws Exception { + rw.markUninteresting(commit); + } + + protected void assertCommit(final RevCommit exp, final RevCommit act) { + assertSame(exp, act); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java new file mode 100644 index 000000000..fb9b358b2 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2008, Google Inc. + * Copyright (C) 2008, Mike Ralphson + * Copyright (C) 2008, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.Set; + +import org.eclipse.jgit.errors.MissingBundlePrerequisiteException; +import org.eclipse.jgit.errors.NotSupportedException; +import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; + +public class BundleWriterTest extends RepositoryTestCase { + + public void testWrite0() throws Exception { + // Create a tiny bundle, (well one of) the first commits only + final byte[] bundle = makeBundle("refs/heads/firstcommit", + "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", null); + + // Then we clone a new repo from that bundle and do a simple test. This + // makes sure + // we could read the bundle we created. + Repository newRepo = createNewEmptyRepo(); + FetchResult fetchResult = fetchFromBundle(newRepo, bundle); + Ref advertisedRef = fetchResult + .getAdvertisedRef("refs/heads/firstcommit"); + + // We expect firstcommit to appear by id + assertEquals("42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", advertisedRef + .getObjectId().name()); + // ..and by name as the bundle created a new ref + assertEquals("42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", newRepo + .resolve("refs/heads/firstcommit").name()); + } + + /** + * Incremental bundle test + * + * @throws Exception + */ + public void testWrite1() throws Exception { + byte[] bundle; + + // Create a small bundle, an early commit + bundle = makeBundle("refs/heads/aa", db.resolve("a").name(), null); + + // Then we clone a new repo from that bundle and do a simple test. This + // makes sure + // we could read the bundle we created. + Repository newRepo = createNewEmptyRepo(); + FetchResult fetchResult = fetchFromBundle(newRepo, bundle); + Ref advertisedRef = fetchResult.getAdvertisedRef("refs/heads/aa"); + + assertEquals(db.resolve("a").name(), advertisedRef.getObjectId().name()); + assertEquals(db.resolve("a").name(), newRepo.resolve("refs/heads/aa") + .name()); + assertNull(newRepo.resolve("refs/heads/a")); + + // Next an incremental bundle + bundle = makeBundle("refs/heads/cc", db.resolve("c").name(), + new RevWalk(db).parseCommit(db.resolve("a").toObjectId())); + fetchResult = fetchFromBundle(newRepo, bundle); + advertisedRef = fetchResult.getAdvertisedRef("refs/heads/cc"); + assertEquals(db.resolve("c").name(), advertisedRef.getObjectId().name()); + assertEquals(db.resolve("c").name(), newRepo.resolve("refs/heads/cc") + .name()); + assertNull(newRepo.resolve("refs/heads/c")); + assertNull(newRepo.resolve("refs/heads/a")); // still unknown + + try { + // Check that we actually needed the first bundle + Repository newRepo2 = createNewEmptyRepo(); + fetchResult = fetchFromBundle(newRepo2, bundle); + fail("We should not be able to fetch from bundle with prerequisites that are not fulfilled"); + } catch (MissingBundlePrerequisiteException e) { + assertTrue(e.getMessage() + .indexOf(db.resolve("refs/heads/a").name()) >= 0); + } + } + + private FetchResult fetchFromBundle(final Repository newRepo, + final byte[] bundle) throws URISyntaxException, + NotSupportedException, TransportException { + final URIish uri = new URIish("in-memory://"); + final ByteArrayInputStream in = new ByteArrayInputStream(bundle); + final RefSpec rs = new RefSpec("refs/heads/*:refs/heads/*"); + final Set refs = Collections.singleton(rs); + return new TransportBundleStream(newRepo, uri, in).fetch( + NullProgressMonitor.INSTANCE, refs); + } + + private byte[] makeBundle(final String name, + final String anObjectToInclude, final RevCommit assume) + throws FileNotFoundException, IOException { + final BundleWriter bw; + + bw = new BundleWriter(db, NullProgressMonitor.INSTANCE); + bw.include(name, ObjectId.fromString(anObjectToInclude)); + if (assume != null) + bw.assume(assume); + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + bw.writeBundle(out); + return out.toByteArray(); + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/IndexPackTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/IndexPackTest.java new file mode 100644 index 000000000..78f4393c3 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/IndexPackTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2008, Imran M Yousuf + * Copyright (C) 2007-2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.IOException; +import java.io.InputStream; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.PackFile; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.lib.TextProgressMonitor; +import org.eclipse.jgit.util.JGitTestUtil; + +/** + * Test indexing of git packs. A pack is read from a stream, copied + * to a new pack and an index is created. Then the packs are tested + * to make sure they contain the expected objects (well we don't test + * for all of them unless the packs are very small). + */ +public class IndexPackTest extends RepositoryTestCase { + + /** + * Test indexing one of the test packs in the egit repo. It has deltas. + * + * @throws IOException + */ + public void test1() throws IOException { + File packFile = JGitTestUtil.getTestResourceFile("pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.pack"); + final InputStream is = new FileInputStream(packFile); + try { + IndexPack pack = new IndexPack(db, is, new File(trash, "tmp_pack1")); + pack.index(new TextProgressMonitor()); + PackFile file = new PackFile(new File(trash, "tmp_pack1.idx"), new File(trash, "tmp_pack1.pack")); + assertTrue(file.hasObject(ObjectId.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904"))); + assertTrue(file.hasObject(ObjectId.fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"))); + assertTrue(file.hasObject(ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"))); + assertTrue(file.hasObject(ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3"))); + assertTrue(file.hasObject(ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"))); + assertTrue(file.hasObject(ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"))); + assertTrue(file.hasObject(ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"))); + assertTrue(file.hasObject(ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"))); + } finally { + is.close(); + } + } + + /** + * This is just another pack. It so happens that we have two convenient pack to + * test with in the repository. + * + * @throws IOException + */ + public void test2() throws IOException { + File packFile = JGitTestUtil.getTestResourceFile("pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.pack"); + final InputStream is = new FileInputStream(packFile); + try { + IndexPack pack = new IndexPack(db, is, new File(trash, "tmp_pack2")); + pack.index(new TextProgressMonitor()); + PackFile file = new PackFile(new File(trash, "tmp_pack2.idx"), new File(trash, "tmp_pack2.pack")); + assertTrue(file.hasObject(ObjectId.fromString("02ba32d3649e510002c21651936b7077aa75ffa9"))); + assertTrue(file.hasObject(ObjectId.fromString("0966a434eb1a025db6b71485ab63a3bfbea520b6"))); + assertTrue(file.hasObject(ObjectId.fromString("09efc7e59a839528ac7bda9fa020dc9101278680"))); + assertTrue(file.hasObject(ObjectId.fromString("0a3d7772488b6b106fb62813c4d6d627918d9181"))); + assertTrue(file.hasObject(ObjectId.fromString("1004d0d7ac26fbf63050a234c9b88a46075719d3"))); + assertTrue(file.hasObject(ObjectId.fromString("10da5895682013006950e7da534b705252b03be6"))); + assertTrue(file.hasObject(ObjectId.fromString("1203b03dc816ccbb67773f28b3c19318654b0bc8"))); + assertTrue(file.hasObject(ObjectId.fromString("15fae9e651043de0fd1deef588aa3fbf5a7a41c6"))); + assertTrue(file.hasObject(ObjectId.fromString("16f9ec009e5568c435f473ba3a1df732d49ce8c3"))); + assertTrue(file.hasObject(ObjectId.fromString("1fd7d579fb6ae3fe942dc09c2c783443d04cf21e"))); + assertTrue(file.hasObject(ObjectId.fromString("20a8ade77639491ea0bd667bf95de8abf3a434c8"))); + assertTrue(file.hasObject(ObjectId.fromString("2675188fd86978d5bc4d7211698b2118ae3bf658"))); + // and lots more... + } finally { + is.close(); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java new file mode 100644 index 000000000..abb6fe0db --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/LongMapTest.java @@ -0,0 +1,138 @@ +/* + * 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; + +import junit.framework.TestCase; + +public class LongMapTest extends TestCase { + private LongMap map; + + protected void setUp() throws Exception { + super.setUp(); + map = new LongMap(); + } + + public void testEmptyMap() { + assertFalse(map.containsKey(0)); + assertFalse(map.containsKey(1)); + + assertNull(map.get(0)); + assertNull(map.get(1)); + + assertNull(map.remove(0)); + assertNull(map.remove(1)); + } + + public void testInsertMinValue() { + final Long min = Long.valueOf(Long.MIN_VALUE); + assertNull(map.put(Long.MIN_VALUE, min)); + assertTrue(map.containsKey(Long.MIN_VALUE)); + assertSame(min, map.get(Long.MIN_VALUE)); + assertFalse(map.containsKey(Integer.MIN_VALUE)); + } + + public void testReplaceMaxValue() { + final Long min = Long.valueOf(Long.MAX_VALUE); + final Long one = Long.valueOf(1); + assertNull(map.put(Long.MAX_VALUE, min)); + assertSame(min, map.get(Long.MAX_VALUE)); + assertSame(min, map.put(Long.MAX_VALUE, one)); + assertSame(one, map.get(Long.MAX_VALUE)); + } + + public void testRemoveOne() { + final long start = 1; + assertNull(map.put(start, Long.valueOf(start))); + assertEquals(Long.valueOf(start), map.remove(start)); + assertFalse(map.containsKey(start)); + } + + public void testRemoveCollision1() { + // This test relies upon the fact that we always >>> 1 the value + // to derive an unsigned hash code. Thus, 0 and 1 fall into the + // same hash bucket. Further it relies on the fact that we add + // the 2nd put at the top of the chain, so removing the 1st will + // cause a different code path. + // + assertNull(map.put(0, Long.valueOf(0))); + assertNull(map.put(1, Long.valueOf(1))); + assertEquals(Long.valueOf(0), map.remove(0)); + + assertFalse(map.containsKey(0)); + assertTrue(map.containsKey(1)); + } + + public void testRemoveCollision2() { + // This test relies upon the fact that we always >>> 1 the value + // to derive an unsigned hash code. Thus, 0 and 1 fall into the + // same hash bucket. Further it relies on the fact that we add + // the 2nd put at the top of the chain, so removing the 2nd will + // cause a different code path. + // + assertNull(map.put(0, Long.valueOf(0))); + assertNull(map.put(1, Long.valueOf(1))); + assertEquals(Long.valueOf(1), map.remove(1)); + + assertTrue(map.containsKey(0)); + assertFalse(map.containsKey(1)); + } + + public void testSmallMap() { + final long start = 12; + final long n = 8; + for (long i = start; i < start + n; i++) + assertNull(map.put(i, Long.valueOf(i))); + for (long i = start; i < start + n; i++) + assertEquals(Long.valueOf(i), map.get(i)); + } + + public void testLargeMap() { + final long start = Integer.MAX_VALUE; + final long n = 100000; + for (long i = start; i < start + n; i++) + assertNull(map.put(i, Long.valueOf(i))); + for (long i = start; i < start + n; i++) + assertEquals(Long.valueOf(i), map.get(i)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java new file mode 100644 index 000000000..f66e2fd33 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/OpenSshConfigTest.java @@ -0,0 +1,228 @@ +/* + * 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.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; + +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.transport.OpenSshConfig.Host; + +public class OpenSshConfigTest extends RepositoryTestCase { + private File home; + + private File configFile; + + private OpenSshConfig osc; + + public void setUp() throws Exception { + super.setUp(); + + home = new File(trash, "home"); + home.mkdir(); + + configFile = new File(new File(home, ".ssh"), "config"); + configFile.getParentFile().mkdir(); + + System.setProperty("user.name", "jex_junit"); + osc = new OpenSshConfig(home, configFile); + } + + private void config(final String data) throws IOException { + final OutputStreamWriter fw = new OutputStreamWriter( + new FileOutputStream(configFile), "UTF-8"); + fw.write(data); + fw.close(); + } + + public void testNoConfig() { + final Host h = osc.lookup("repo.or.cz"); + assertNotNull(h); + assertEquals("repo.or.cz", h.getHostName()); + assertEquals("jex_junit", h.getUser()); + assertEquals(22, h.getPort()); + assertNull(h.getIdentityFile()); + } + + public void testSeparatorParsing() throws Exception { + config("Host\tfirst\n" + + "\tHostName\tfirst.tld\n" + + "\n" + + "Host second\n" + + " HostName\tsecond.tld\n" + + "Host=third\n" + + "HostName=third.tld\n\n\n" + + "\t Host = fourth\n\n\n" + + " \t HostName\t=fourth.tld\n" + + "Host\t = last\n" + + "HostName \t last.tld"); + assertNotNull(osc.lookup("first")); + assertEquals("first.tld", osc.lookup("first").getHostName()); + assertNotNull(osc.lookup("second")); + assertEquals("second.tld", osc.lookup("second").getHostName()); + assertNotNull(osc.lookup("third")); + assertEquals("third.tld", osc.lookup("third").getHostName()); + assertNotNull(osc.lookup("fourth")); + assertEquals("fourth.tld", osc.lookup("fourth").getHostName()); + assertNotNull(osc.lookup("last")); + assertEquals("last.tld", osc.lookup("last").getHostName()); + } + + public void testQuoteParsing() throws Exception { + config("Host \"good\"\n" + + " HostName=\"good.tld\"\n" + + " Port=\"6007\"\n" + + " User=\"gooduser\"\n" + + "Host multiple unquoted and \"quoted\" \"hosts\"\n" + + " Port=\"2222\"\n" + + "Host \"spaced\"\n" + + "# Bad host name, but testing preservation of spaces\n" + + " HostName=\" spaced\ttld \"\n" + + "# Misbalanced quotes\n" + + "Host \"bad\"\n" + + "# OpenSSH doesn't allow this but ...\n" + + " HostName=bad.tld\"\n"); + assertEquals("good.tld", osc.lookup("good").getHostName()); + assertEquals("gooduser", osc.lookup("good").getUser()); + assertEquals(6007, osc.lookup("good").getPort()); + assertEquals(2222, osc.lookup("multiple").getPort()); + assertEquals(2222, osc.lookup("quoted").getPort()); + assertEquals(2222, osc.lookup("and").getPort()); + assertEquals(2222, osc.lookup("unquoted").getPort()); + assertEquals(2222, osc.lookup("hosts").getPort()); + assertEquals(" spaced\ttld ", osc.lookup("spaced").getHostName()); + assertEquals("bad.tld\"", osc.lookup("bad").getHostName()); + } + + public void testAlias_DoesNotMatch() throws Exception { + config("Host orcz\n" + "\tHostName repo.or.cz\n"); + final Host h = osc.lookup("repo.or.cz"); + assertNotNull(h); + assertEquals("repo.or.cz", h.getHostName()); + assertEquals("jex_junit", h.getUser()); + assertEquals(22, h.getPort()); + assertNull(h.getIdentityFile()); + } + + public void testAlias_OptionsSet() throws Exception { + config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\tPort 2222\n" + + "\tUser jex\n" + "\tIdentityFile .ssh/id_jex\n" + + "\tForwardX11 no\n"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertEquals("repo.or.cz", h.getHostName()); + assertEquals("jex", h.getUser()); + assertEquals(2222, h.getPort()); + assertEquals(new File(home, ".ssh/id_jex"), h.getIdentityFile()); + } + + public void testAlias_OptionsKeywordCaseInsensitive() throws Exception { + config("hOsT orcz\n" + "\thOsTnAmE repo.or.cz\n" + "\tPORT 2222\n" + + "\tuser jex\n" + "\tidentityfile .ssh/id_jex\n" + + "\tForwardX11 no\n"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertEquals("repo.or.cz", h.getHostName()); + assertEquals("jex", h.getUser()); + assertEquals(2222, h.getPort()); + assertEquals(new File(home, ".ssh/id_jex"), h.getIdentityFile()); + } + + public void testAlias_OptionsInherit() throws Exception { + config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n" + + "\tHostName not.a.host.example.com\n" + "\tPort 2222\n" + + "\tUser jex\n" + "\tIdentityFile .ssh/id_jex\n" + + "\tForwardX11 no\n"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertEquals("repo.or.cz", h.getHostName()); + assertEquals("jex", h.getUser()); + assertEquals(2222, h.getPort()); + assertEquals(new File(home, ".ssh/id_jex"), h.getIdentityFile()); + } + + public void testAlias_PreferredAuthenticationsDefault() throws Exception { + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertNull(h.getPreferredAuthentications()); + } + + public void testAlias_PreferredAuthentications() throws Exception { + config("Host orcz\n" + "\tPreferredAuthentications publickey\n"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertEquals("publickey", h.getPreferredAuthentications()); + } + + public void testAlias_InheritPreferredAuthentications() throws Exception { + config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n" + + "\tPreferredAuthentications publickey, hostbased\n"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertEquals("publickey,hostbased", h.getPreferredAuthentications()); + } + + public void testAlias_BatchModeDefault() throws Exception { + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertEquals(false, h.isBatchMode()); + } + + public void testAlias_BatchModeYes() throws Exception { + config("Host orcz\n" + "\tBatchMode yes\n"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertEquals(true, h.isBatchMode()); + } + + public void testAlias_InheritBatchMode() throws Exception { + config("Host orcz\n" + "\tHostName repo.or.cz\n" + "\n" + "Host *\n" + + "\tBatchMode yes\n"); + final Host h = osc.lookup("orcz"); + assertNotNull(h); + assertEquals(true, h.isBatchMode()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineInTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineInTest.java new file mode 100644 index 000000000..1bcac9e37 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineInTest.java @@ -0,0 +1,268 @@ +/* + * 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; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import junit.framework.TestCase; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.MutableObjectId; +import org.eclipse.jgit.lib.ObjectId; + +// Note, test vectors created with: +// +// perl -e 'printf "%4.4x%s\n", 4+length($ARGV[0]),$ARGV[0]' + +public class PacketLineInTest extends TestCase { + private ByteArrayInputStream rawIn; + + private PacketLineIn in; + + // readString + + public void testReadString1() throws IOException { + init("0006a\n0007bc\n"); + assertEquals("a", in.readString()); + assertEquals("bc", in.readString()); + assertEOF(); + } + + public void testReadString2() throws IOException { + init("0032want fcfcfb1fd94829c1a1704f894fc111d14770d34e\n"); + final String act = in.readString(); + assertEquals("want fcfcfb1fd94829c1a1704f894fc111d14770d34e", act); + assertEOF(); + } + + public void testReadString4() throws IOException { + init("0005a0006bc"); + assertEquals("a", in.readString()); + assertEquals("bc", in.readString()); + assertEOF(); + } + + public void testReadString5() throws IOException { + // accept both upper and lower case + init("000Fhi i am a s"); + assertEquals("hi i am a s", in.readString()); + assertEOF(); + + init("000fhi i am a s"); + assertEquals("hi i am a s", in.readString()); + assertEOF(); + } + + public void testReadString_LenHELO() { + init("HELO"); + try { + in.readString(); + fail("incorrectly accepted invalid packet header"); + } catch (IOException e) { + assertEquals("Invalid packet line header: HELO", e.getMessage()); + } + } + + public void testReadString_Len0001() { + init("0001"); + try { + in.readString(); + fail("incorrectly accepted invalid packet header"); + } catch (IOException e) { + assertEquals("Invalid packet line header: 0001", e.getMessage()); + } + } + + public void testReadString_Len0002() { + init("0002"); + try { + in.readString(); + fail("incorrectly accepted invalid packet header"); + } catch (IOException e) { + assertEquals("Invalid packet line header: 0002", e.getMessage()); + } + } + + public void testReadString_Len0003() { + init("0003"); + try { + in.readString(); + fail("incorrectly accepted invalid packet header"); + } catch (IOException e) { + assertEquals("Invalid packet line header: 0003", e.getMessage()); + } + } + + public void testReadString_Len0004() throws IOException { + init("0004"); + final String act = in.readString(); + assertEquals("", act); + assertNotSame(PacketLineIn.END, act); + assertEOF(); + } + + public void testReadString_End() throws IOException { + init("0000"); + assertSame(PacketLineIn.END, in.readString()); + assertEOF(); + } + + // readStringNoLF + + public void testReadStringRaw1() throws IOException { + init("0005a0006bc"); + assertEquals("a", in.readStringRaw()); + assertEquals("bc", in.readStringRaw()); + assertEOF(); + } + + public void testReadStringRaw2() throws IOException { + init("0031want fcfcfb1fd94829c1a1704f894fc111d14770d34e"); + final String act = in.readStringRaw(); + assertEquals("want fcfcfb1fd94829c1a1704f894fc111d14770d34e", act); + assertEOF(); + } + + public void testReadStringRaw3() throws IOException { + init("0004"); + final String act = in.readStringRaw(); + assertEquals("", act); + assertNotSame(PacketLineIn.END, act); + assertEOF(); + } + + public void testReadStringRaw_End() throws IOException { + init("0000"); + assertSame(PacketLineIn.END, in.readStringRaw()); + assertEOF(); + } + + public void testReadStringRaw4() { + init("HELO"); + try { + in.readStringRaw(); + fail("incorrectly accepted invalid packet header"); + } catch (IOException e) { + assertEquals("Invalid packet line header: HELO", e.getMessage()); + } + } + + // readACK + + public void testReadACK_NAK() throws IOException { + final ObjectId expid = ObjectId + .fromString("fcfcfb1fd94829c1a1704f894fc111d14770d34e"); + final MutableObjectId actid = new MutableObjectId(); + actid.fromString(expid.name()); + + init("0008NAK\n"); + assertSame(PacketLineIn.AckNackResult.NAK, in.readACK(actid)); + assertTrue(actid.equals(expid)); + assertEOF(); + } + + public void testReadACK_ACK1() throws IOException { + final ObjectId expid = ObjectId + .fromString("fcfcfb1fd94829c1a1704f894fc111d14770d34e"); + final MutableObjectId actid = new MutableObjectId(); + + init("0031ACK fcfcfb1fd94829c1a1704f894fc111d14770d34e\n"); + assertSame(PacketLineIn.AckNackResult.ACK, in.readACK(actid)); + assertTrue(actid.equals(expid)); + assertEOF(); + } + + public void testReadACK_ACKcontinue1() throws IOException { + final ObjectId expid = ObjectId + .fromString("fcfcfb1fd94829c1a1704f894fc111d14770d34e"); + final MutableObjectId actid = new MutableObjectId(); + + init("003aACK fcfcfb1fd94829c1a1704f894fc111d14770d34e continue\n"); + assertSame(PacketLineIn.AckNackResult.ACK_CONTINUE, in.readACK(actid)); + assertTrue(actid.equals(expid)); + assertEOF(); + } + + public void testReadACK_Invalid1() { + init("HELO"); + try { + in.readACK(new MutableObjectId()); + fail("incorrectly accepted invalid packet header"); + } catch (IOException e) { + assertEquals("Invalid packet line header: HELO", e.getMessage()); + } + } + + public void testReadACK_Invalid2() { + init("0009HELO\n"); + try { + in.readACK(new MutableObjectId()); + fail("incorrectly accepted invalid ACK/NAK"); + } catch (IOException e) { + assertEquals("Expected ACK/NAK, got: HELO", e.getMessage()); + } + } + + public void testReadACK_Invalid3() { + init("0000"); + try { + in.readACK(new MutableObjectId()); + fail("incorrectly accepted no ACK/NAK"); + } catch (IOException e) { + assertEquals("Expected ACK/NAK, found EOF", e.getMessage()); + } + } + + // test support + + private void init(final String msg) { + rawIn = new ByteArrayInputStream(Constants.encodeASCII(msg)); + in = new PacketLineIn(rawIn); + } + + private void assertEOF() { + assertEquals(-1, rawIn.read()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineOutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineOutTest.java new file mode 100644 index 000000000..6eb98ac12 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PacketLineOutTest.java @@ -0,0 +1,181 @@ +/* + * 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; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import junit.framework.TestCase; + +import org.eclipse.jgit.lib.Constants; + +// Note, test vectors created with: +// +// perl -e 'printf "%4.4x%s\n", 4+length($ARGV[0]),$ARGV[0]' + +public class PacketLineOutTest extends TestCase { + private ByteArrayOutputStream rawOut; + + private PacketLineOut out; + + protected void setUp() throws Exception { + super.setUp(); + rawOut = new ByteArrayOutputStream(); + out = new PacketLineOut(rawOut); + } + + // writeString + + public void testWriteString1() throws IOException { + out.writeString("a"); + out.writeString("bc"); + assertBuffer("0005a0006bc"); + } + + public void testWriteString2() throws IOException { + out.writeString("a\n"); + out.writeString("bc\n"); + assertBuffer("0006a\n0007bc\n"); + } + + public void testWriteString3() throws IOException { + out.writeString(""); + assertBuffer("0004"); + } + + // end + + public void testWriteEnd() throws IOException { + final int[] flushCnt = new int[1]; + final OutputStream mockout = new OutputStream() { + @Override + public void write(int arg0) throws IOException { + rawOut.write(arg0); + } + + @Override + public void flush() throws IOException { + flushCnt[0]++; + } + }; + + new PacketLineOut(mockout).end(); + assertBuffer("0000"); + assertEquals(1, flushCnt[0]); + } + + // writePacket + + public void testWritePacket1() throws IOException { + out.writePacket(new byte[] { 'a' }); + assertBuffer("0005a"); + } + + public void testWritePacket2() throws IOException { + out.writePacket(new byte[] { 'a', 'b', 'c', 'd' }); + assertBuffer("0008abcd"); + } + + public void testWritePacket3() throws IOException { + final int buflen = SideBandOutputStream.MAX_BUF + - SideBandOutputStream.HDR_SIZE; + final byte[] buf = new byte[buflen]; + for (int i = 0; i < buf.length; i++) { + buf[i] = (byte) i; + } + out.writePacket(buf); + out.flush(); + + final byte[] act = rawOut.toByteArray(); + final String explen = Integer.toString(buf.length + 4, 16); + assertEquals(4 + buf.length, act.length); + assertEquals(new String(act, 0, 4, "UTF-8"), explen); + for (int i = 0, j = 4; i < buf.length; i++, j++) { + assertEquals(buf[i], act[j]); + } + } + + // writeChannelPacket + + public void testWriteChannelPacket1() throws IOException { + out.writeChannelPacket(1, new byte[] { 'a' }, 0, 1); + assertBuffer("0006\001a"); + } + + public void testWriteChannelPacket2() throws IOException { + out.writeChannelPacket(2, new byte[] { 'b' }, 0, 1); + assertBuffer("0006\002b"); + } + + public void testWriteChannelPacket3() throws IOException { + out.writeChannelPacket(3, new byte[] { 'c' }, 0, 1); + assertBuffer("0006\003c"); + } + + // flush + + public void testFlush() throws IOException { + final int[] flushCnt = new int[1]; + final OutputStream mockout = new OutputStream() { + @Override + public void write(int arg0) throws IOException { + fail("should not write"); + } + + @Override + public void flush() throws IOException { + flushCnt[0]++; + } + }; + + new PacketLineOut(mockout).flush(); + assertEquals(1, flushCnt[0]); + } + + private void assertBuffer(final String exp) throws IOException { + assertEquals(exp, new String(rawOut.toByteArray(), + Constants.CHARACTER_ENCODING)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java new file mode 100644 index 000000000..4a47456f6 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.HashMap; +import java.util.HashSet; +import java.util.Map; + +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.lib.Repository; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.lib.TextProgressMonitor; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.transport.RemoteRefUpdate.Status; + +public class PushProcessTest extends RepositoryTestCase { + private PushProcess process; + + private MockTransport transport; + + private HashSet refUpdates; + + private HashSet advertisedRefs; + + private Status connectionUpdateStatus; + + @Override + public void setUp() throws Exception { + super.setUp(); + transport = new MockTransport(db, new URIish()); + refUpdates = new HashSet(); + advertisedRefs = new HashSet(); + connectionUpdateStatus = Status.OK; + } + + /** + * Test for fast-forward remote update. + * + * @throws IOException + */ + public void testUpdateFastForward() throws IOException { + final RemoteRefUpdate rru = new RemoteRefUpdate(db, + "2c349335b7f797072cf729c4f3bb0914ecb6dec9", + "refs/heads/master", false, null, null); + final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master", + ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef")); + testOneUpdateStatus(rru, ref, Status.OK, true); + } + + /** + * Test for non fast-forward remote update, when remote object is not known + * to local repository. + * + * @throws IOException + */ + public void testUpdateNonFastForwardUnknownObject() throws IOException { + final RemoteRefUpdate rru = new RemoteRefUpdate(db, + "2c349335b7f797072cf729c4f3bb0914ecb6dec9", + "refs/heads/master", false, null, null); + final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master", + ObjectId.fromString("0000000000000000000000000000000000000001")); + testOneUpdateStatus(rru, ref, Status.REJECTED_NONFASTFORWARD, null); + } + + /** + * Test for non fast-forward remote update, when remote object is known to + * local repository, but it is not an ancestor of new object. + * + * @throws IOException + */ + public void testUpdateNonFastForward() throws IOException { + final RemoteRefUpdate rru = new RemoteRefUpdate(db, + "ac7e7e44c1885efb472ad54a78327d66bfc4ecef", + "refs/heads/master", false, null, null); + final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master", + ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9")); + testOneUpdateStatus(rru, ref, Status.REJECTED_NONFASTFORWARD, null); + } + + /** + * Test for non fast-forward remote update, when force update flag is set. + * + * @throws IOException + */ + public void testUpdateNonFastForwardForced() throws IOException { + final RemoteRefUpdate rru = new RemoteRefUpdate(db, + "ac7e7e44c1885efb472ad54a78327d66bfc4ecef", + "refs/heads/master", true, null, null); + final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master", + ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9")); + testOneUpdateStatus(rru, ref, Status.OK, false); + } + + /** + * Test for remote ref creation. + * + * @throws IOException + */ + public void testUpdateCreateRef() throws IOException { + final RemoteRefUpdate rru = new RemoteRefUpdate(db, + "ac7e7e44c1885efb472ad54a78327d66bfc4ecef", + "refs/heads/master", false, null, null); + testOneUpdateStatus(rru, null, Status.OK, true); + } + + /** + * Test for remote ref deletion. + * + * @throws IOException + */ + public void testUpdateDelete() throws IOException { + final RemoteRefUpdate rru = new RemoteRefUpdate(db, null, + "refs/heads/master", false, null, null); + final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master", + ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9")); + testOneUpdateStatus(rru, ref, Status.OK, true); + } + + /** + * Test for remote ref deletion (try), when that ref doesn't exist on remote + * repo. + * + * @throws IOException + */ + public void testUpdateDeleteNonExisting() throws IOException { + final RemoteRefUpdate rru = new RemoteRefUpdate(db, null, + "refs/heads/master", false, null, null); + testOneUpdateStatus(rru, null, Status.NON_EXISTING, null); + } + + /** + * Test for remote ref update, when it is already up to date. + * + * @throws IOException + */ + public void testUpdateUpToDate() throws IOException { + final RemoteRefUpdate rru = new RemoteRefUpdate(db, + "2c349335b7f797072cf729c4f3bb0914ecb6dec9", + "refs/heads/master", false, null, null); + final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master", + ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9")); + testOneUpdateStatus(rru, ref, Status.UP_TO_DATE, null); + } + + /** + * Test for remote ref update with expected remote object. + * + * @throws IOException + */ + public void testUpdateExpectedRemote() throws IOException { + final RemoteRefUpdate rru = new RemoteRefUpdate(db, + "2c349335b7f797072cf729c4f3bb0914ecb6dec9", + "refs/heads/master", false, null, ObjectId + .fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef")); + final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master", + ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef")); + testOneUpdateStatus(rru, ref, Status.OK, true); + } + + /** + * Test for remote ref update with expected old object set, when old object + * is not that expected one. + * + * @throws IOException + */ + public void testUpdateUnexpectedRemote() throws IOException { + final RemoteRefUpdate rru = new RemoteRefUpdate(db, + "2c349335b7f797072cf729c4f3bb0914ecb6dec9", + "refs/heads/master", false, null, ObjectId + .fromString("0000000000000000000000000000000000000001")); + final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master", + ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef")); + testOneUpdateStatus(rru, ref, Status.REJECTED_REMOTE_CHANGED, null); + } + + /** + * Test for remote ref update with expected old object set, when old object + * is not that expected one and force update flag is set (which should have + * lower priority) - shouldn't change behavior. + * + * @throws IOException + */ + public void testUpdateUnexpectedRemoteVsForce() throws IOException { + final RemoteRefUpdate rru = new RemoteRefUpdate(db, + "2c349335b7f797072cf729c4f3bb0914ecb6dec9", + "refs/heads/master", true, null, ObjectId + .fromString("0000000000000000000000000000000000000001")); + final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master", + ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef")); + testOneUpdateStatus(rru, ref, Status.REJECTED_REMOTE_CHANGED, null); + } + + /** + * Test for remote ref update, when connection rejects update. + * + * @throws IOException + */ + public void testUpdateRejectedByConnection() throws IOException { + connectionUpdateStatus = Status.REJECTED_OTHER_REASON; + final RemoteRefUpdate rru = new RemoteRefUpdate(db, + "2c349335b7f797072cf729c4f3bb0914ecb6dec9", + "refs/heads/master", false, null, null); + final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master", + ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef")); + testOneUpdateStatus(rru, ref, Status.REJECTED_OTHER_REASON, null); + } + + /** + * Test for remote refs updates with mixed cases that shouldn't depend on + * each other. + * + * @throws IOException + */ + public void testUpdateMixedCases() throws IOException { + final RemoteRefUpdate rruOk = new RemoteRefUpdate(db, null, + "refs/heads/master", false, null, null); + final Ref refToChange = new Ref(Ref.Storage.LOOSE, "refs/heads/master", + ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9")); + final RemoteRefUpdate rruReject = new RemoteRefUpdate(db, null, + "refs/heads/nonexisting", false, null, null); + refUpdates.add(rruOk); + refUpdates.add(rruReject); + advertisedRefs.add(refToChange); + executePush(); + assertEquals(Status.OK, rruOk.getStatus()); + assertEquals(true, rruOk.isFastForward()); + assertEquals(Status.NON_EXISTING, rruReject.getStatus()); + } + + /** + * Test for local tracking ref update. + * + * @throws IOException + */ + public void testTrackingRefUpdateEnabled() throws IOException { + final RemoteRefUpdate rru = new RemoteRefUpdate(db, + "2c349335b7f797072cf729c4f3bb0914ecb6dec9", + "refs/heads/master", false, "refs/remotes/test/master", null); + final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master", + ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef")); + refUpdates.add(rru); + advertisedRefs.add(ref); + final PushResult result = executePush(); + final TrackingRefUpdate tru = result + .getTrackingRefUpdate("refs/remotes/test/master"); + assertNotNull(tru); + assertEquals("refs/remotes/test/master", tru.getLocalName()); + assertEquals(Result.NEW, tru.getResult()); + } + + /** + * Test for local tracking ref update disabled. + * + * @throws IOException + */ + public void testTrackingRefUpdateDisabled() throws IOException { + final RemoteRefUpdate rru = new RemoteRefUpdate(db, + "2c349335b7f797072cf729c4f3bb0914ecb6dec9", + "refs/heads/master", false, null, null); + final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master", + ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef")); + refUpdates.add(rru); + advertisedRefs.add(ref); + final PushResult result = executePush(); + assertTrue(result.getTrackingRefUpdates().isEmpty()); + } + + /** + * Test for local tracking ref update when remote update has failed. + * + * @throws IOException + */ + public void testTrackingRefUpdateOnReject() throws IOException { + final RemoteRefUpdate rru = new RemoteRefUpdate(db, + "ac7e7e44c1885efb472ad54a78327d66bfc4ecef", + "refs/heads/master", false, null, null); + final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master", + ObjectId.fromString("2c349335b7f797072cf729c4f3bb0914ecb6dec9")); + final PushResult result = testOneUpdateStatus(rru, ref, + Status.REJECTED_NONFASTFORWARD, null); + assertTrue(result.getTrackingRefUpdates().isEmpty()); + } + + /** + * Test for push operation result - that contains expected elements. + * + * @throws IOException + */ + public void testPushResult() throws IOException { + final RemoteRefUpdate rru = new RemoteRefUpdate(db, + "2c349335b7f797072cf729c4f3bb0914ecb6dec9", + "refs/heads/master", false, "refs/remotes/test/master", null); + final Ref ref = new Ref(Ref.Storage.LOOSE, "refs/heads/master", + ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef")); + refUpdates.add(rru); + advertisedRefs.add(ref); + final PushResult result = executePush(); + assertEquals(1, result.getTrackingRefUpdates().size()); + assertEquals(1, result.getAdvertisedRefs().size()); + assertEquals(1, result.getRemoteUpdates().size()); + assertNotNull(result.getTrackingRefUpdate("refs/remotes/test/master")); + assertNotNull(result.getAdvertisedRef("refs/heads/master")); + assertNotNull(result.getRemoteUpdate("refs/heads/master")); + } + + private PushResult testOneUpdateStatus(final RemoteRefUpdate rru, + final Ref advertisedRef, final Status expectedStatus, + Boolean fastForward) throws NotSupportedException, + TransportException { + refUpdates.add(rru); + if (advertisedRef != null) + advertisedRefs.add(advertisedRef); + final PushResult result = executePush(); + assertEquals(expectedStatus, rru.getStatus()); + if (fastForward != null) + assertEquals(fastForward.booleanValue(), rru.isFastForward()); + return result; + } + + private PushResult executePush() throws NotSupportedException, + TransportException { + process = new PushProcess(transport, refUpdates); + return process.execute(new TextProgressMonitor()); + } + + private class MockTransport extends Transport { + MockTransport(Repository local, URIish uri) { + super(local, uri); + } + + @Override + public FetchConnection openFetch() throws NotSupportedException, + TransportException { + throw new NotSupportedException("mock"); + } + + @Override + public PushConnection openPush() throws NotSupportedException, + TransportException { + return new MockPushConnection(); + } + + @Override + public void close() { + // nothing here + } + } + + private class MockPushConnection extends BaseConnection implements + PushConnection { + MockPushConnection() { + final Map refsMap = new HashMap(); + for (final Ref r : advertisedRefs) + refsMap.put(r.getName(), r); + available(refsMap); + } + + @Override + public void close() { + // nothing here + } + + public void push(ProgressMonitor monitor, + Map refsToUpdate) + throws TransportException { + for (final RemoteRefUpdate rru : refsToUpdate.values()) { + assertEquals(Status.NOT_ATTEMPTED, rru.getStatus()); + rru.setStatus(connectionUpdateStatus); + } + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java new file mode 100644 index 000000000..38dbe0962 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RefSpecTest.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 junit.framework.TestCase; + +import org.eclipse.jgit.lib.Ref; + +public class RefSpecTest extends TestCase { + public void testMasterMaster() { + final String sn = "refs/heads/master"; + final RefSpec rs = new RefSpec(sn + ":" + sn); + assertFalse(rs.isForceUpdate()); + assertFalse(rs.isWildcard()); + assertEquals(sn, rs.getSource()); + assertEquals(sn, rs.getDestination()); + assertEquals(sn + ":" + sn, rs.toString()); + assertEquals(rs, new RefSpec(rs.toString())); + + Ref r = new Ref(Ref.Storage.LOOSE, sn, null); + assertTrue(rs.matchSource(r)); + assertTrue(rs.matchDestination(r)); + assertSame(rs, rs.expandFromSource(r)); + + r = new Ref(Ref.Storage.LOOSE, sn + "-and-more", null); + assertFalse(rs.matchSource(r)); + assertFalse(rs.matchDestination(r)); + } + + public void testSplitLastColon() { + final String lhs = ":m:a:i:n:t"; + final String rhs = "refs/heads/maint"; + final RefSpec rs = new RefSpec(lhs + ":" + rhs); + assertFalse(rs.isForceUpdate()); + assertFalse(rs.isWildcard()); + assertEquals(lhs, rs.getSource()); + assertEquals(rhs, rs.getDestination()); + assertEquals(lhs + ":" + rhs, rs.toString()); + assertEquals(rs, new RefSpec(rs.toString())); + } + + public void testForceMasterMaster() { + final String sn = "refs/heads/master"; + final RefSpec rs = new RefSpec("+" + sn + ":" + sn); + assertTrue(rs.isForceUpdate()); + assertFalse(rs.isWildcard()); + assertEquals(sn, rs.getSource()); + assertEquals(sn, rs.getDestination()); + assertEquals("+" + sn + ":" + sn, rs.toString()); + assertEquals(rs, new RefSpec(rs.toString())); + + Ref r = new Ref(Ref.Storage.LOOSE, sn, null); + assertTrue(rs.matchSource(r)); + assertTrue(rs.matchDestination(r)); + assertSame(rs, rs.expandFromSource(r)); + + r = new Ref(Ref.Storage.LOOSE, sn + "-and-more", null); + assertFalse(rs.matchSource(r)); + assertFalse(rs.matchDestination(r)); + } + + public void testMaster() { + final String sn = "refs/heads/master"; + final RefSpec rs = new RefSpec(sn); + assertFalse(rs.isForceUpdate()); + assertFalse(rs.isWildcard()); + assertEquals(sn, rs.getSource()); + assertNull(rs.getDestination()); + assertEquals(sn, rs.toString()); + assertEquals(rs, new RefSpec(rs.toString())); + + Ref r = new Ref(Ref.Storage.LOOSE, sn, null); + assertTrue(rs.matchSource(r)); + assertFalse(rs.matchDestination(r)); + assertSame(rs, rs.expandFromSource(r)); + + r = new Ref(Ref.Storage.LOOSE, sn + "-and-more", null); + assertFalse(rs.matchSource(r)); + assertFalse(rs.matchDestination(r)); + } + + public void testForceMaster() { + final String sn = "refs/heads/master"; + final RefSpec rs = new RefSpec("+" + sn); + assertTrue(rs.isForceUpdate()); + assertFalse(rs.isWildcard()); + assertEquals(sn, rs.getSource()); + assertNull(rs.getDestination()); + assertEquals("+" + sn, rs.toString()); + assertEquals(rs, new RefSpec(rs.toString())); + + Ref r = new Ref(Ref.Storage.LOOSE, sn, null); + assertTrue(rs.matchSource(r)); + assertFalse(rs.matchDestination(r)); + assertSame(rs, rs.expandFromSource(r)); + + r = new Ref(Ref.Storage.LOOSE, sn + "-and-more", null); + assertFalse(rs.matchSource(r)); + assertFalse(rs.matchDestination(r)); + } + + public void testDeleteMaster() { + final String sn = "refs/heads/master"; + final RefSpec rs = new RefSpec(":" + sn); + assertFalse(rs.isForceUpdate()); + assertFalse(rs.isWildcard()); + assertNull(rs.getSource()); + assertEquals(sn, rs.getDestination()); + assertEquals(":" + sn, rs.toString()); + assertEquals(rs, new RefSpec(rs.toString())); + + Ref r = new Ref(Ref.Storage.LOOSE, sn, null); + assertFalse(rs.matchSource(r)); + assertTrue(rs.matchDestination(r)); + assertSame(rs, rs.expandFromSource(r)); + + r = new Ref(Ref.Storage.LOOSE, sn + "-and-more", null); + assertFalse(rs.matchSource(r)); + assertFalse(rs.matchDestination(r)); + } + + public void testForceRemotesOrigin() { + final String srcn = "refs/heads/*"; + final String dstn = "refs/remotes/origin/*"; + final RefSpec rs = new RefSpec("+" + srcn + ":" + dstn); + assertTrue(rs.isForceUpdate()); + assertTrue(rs.isWildcard()); + assertEquals(srcn, rs.getSource()); + assertEquals(dstn, rs.getDestination()); + assertEquals("+" + srcn + ":" + dstn, rs.toString()); + assertEquals(rs, new RefSpec(rs.toString())); + + Ref r; + RefSpec expanded; + + r = new Ref(Ref.Storage.LOOSE, "refs/heads/master", null); + assertTrue(rs.matchSource(r)); + assertFalse(rs.matchDestination(r)); + expanded = rs.expandFromSource(r); + assertNotSame(rs, expanded); + assertTrue(expanded.isForceUpdate()); + assertFalse(expanded.isWildcard()); + assertEquals(r.getName(), expanded.getSource()); + assertEquals("refs/remotes/origin/master", expanded.getDestination()); + + r = new Ref(Ref.Storage.LOOSE, "refs/remotes/origin/next", null); + assertFalse(rs.matchSource(r)); + assertTrue(rs.matchDestination(r)); + + r = new Ref(Ref.Storage.LOOSE, "refs/tags/v1.0", null); + assertFalse(rs.matchSource(r)); + assertFalse(rs.matchDestination(r)); + } + + public void testCreateEmpty() { + final RefSpec rs = new RefSpec(); + assertFalse(rs.isForceUpdate()); + assertFalse(rs.isWildcard()); + assertEquals("HEAD", rs.getSource()); + assertNull(rs.getDestination()); + assertEquals("HEAD", rs.toString()); + } + + public void testSetForceUpdate() { + final String s = "refs/heads/*:refs/remotes/origin/*"; + final RefSpec a = new RefSpec(s); + assertFalse(a.isForceUpdate()); + RefSpec b = a.setForceUpdate(true); + assertNotSame(a, b); + assertFalse(a.isForceUpdate()); + assertTrue(b.isForceUpdate()); + assertEquals(s, a.toString()); + assertEquals("+" + s, b.toString()); + } + + public void testSetSource() { + final RefSpec a = new RefSpec(); + final RefSpec b = a.setSource("refs/heads/master"); + assertNotSame(a, b); + assertEquals("HEAD", a.toString()); + assertEquals("refs/heads/master", b.toString()); + } + + public void testSetDestination() { + final RefSpec a = new RefSpec(); + final RefSpec b = a.setDestination("refs/heads/master"); + assertNotSame(a, b); + assertEquals("HEAD", a.toString()); + assertEquals("HEAD:refs/heads/master", b.toString()); + } + + public void testSetDestination_SourceNull() { + final RefSpec a = new RefSpec(); + RefSpec b; + + b = a.setDestination("refs/heads/master"); + b = b.setSource(null); + assertNotSame(a, b); + assertEquals("HEAD", a.toString()); + assertEquals(":refs/heads/master", b.toString()); + } + + public void testSetSourceDestination() { + final RefSpec a = new RefSpec(); + final RefSpec b; + b = a.setSourceDestination("refs/heads/*", "refs/remotes/origin/*"); + assertNotSame(a, b); + assertEquals("HEAD", a.toString()); + assertEquals("refs/heads/*:refs/remotes/origin/*", b.toString()); + } + + public void testExpandFromDestination_NonWildcard() { + final String src = "refs/heads/master"; + final String dst = "refs/remotes/origin/master"; + final RefSpec a = new RefSpec(src + ":" + dst); + final RefSpec r = a.expandFromDestination(dst); + assertSame(a, r); + assertFalse(r.isWildcard()); + assertEquals(src, r.getSource()); + assertEquals(dst, r.getDestination()); + } + + public void testExpandFromDestination_Wildcard() { + final String src = "refs/heads/master"; + final String dst = "refs/remotes/origin/master"; + final RefSpec a = new RefSpec("refs/heads/*:refs/remotes/origin/*"); + final RefSpec r = a.expandFromDestination(dst); + assertNotSame(a, r); + assertFalse(r.isWildcard()); + assertEquals(src, r.getSource()); + assertEquals(dst, r.getDestination()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java new file mode 100644 index 000000000..3d9b51f9b --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/RemoteConfigTest.java @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.List; + +import junit.framework.TestCase; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.lib.Config; + +public class RemoteConfigTest extends TestCase { + private Config config; + + protected void setUp() throws Exception { + super.setUp(); + config = new Config(); + } + + private void readConfig(final String dat) throws ConfigInvalidException { + config = new Config(); + config.fromText(dat); + } + + private void checkConfig(final String exp) { + assertEquals(exp, config.toText()); + } + + private static void assertEquals(final String exp, final URIish act) { + assertEquals(exp, act != null ? act.toString() : null); + } + + public void testSimple() throws Exception { + readConfig("[remote \"spearce\"]\n" + + "url = http://www.spearce.org/egit.git\n" + + "fetch = +refs/heads/*:refs/remotes/spearce/*\n"); + + final RemoteConfig rc = new RemoteConfig(config, "spearce"); + final List allURIs = rc.getURIs(); + RefSpec spec; + + assertEquals("spearce", rc.getName()); + assertNotNull(allURIs); + assertNotNull(rc.getFetchRefSpecs()); + assertNotNull(rc.getPushRefSpecs()); + assertNotNull(rc.getTagOpt()); + assertEquals(0, rc.getTimeout()); + assertSame(TagOpt.AUTO_FOLLOW, rc.getTagOpt()); + + assertEquals(1, allURIs.size()); + assertEquals("http://www.spearce.org/egit.git", allURIs.get(0)); + + assertEquals(1, rc.getFetchRefSpecs().size()); + spec = rc.getFetchRefSpecs().get(0); + assertTrue(spec.isForceUpdate()); + assertTrue(spec.isWildcard()); + assertEquals("refs/heads/*", spec.getSource()); + assertEquals("refs/remotes/spearce/*", spec.getDestination()); + + assertEquals(0, rc.getPushRefSpecs().size()); + } + + public void testSimpleNoTags() throws Exception { + readConfig("[remote \"spearce\"]\n" + + "url = http://www.spearce.org/egit.git\n" + + "fetch = +refs/heads/*:refs/remotes/spearce/*\n" + + "tagopt = --no-tags\n"); + final RemoteConfig rc = new RemoteConfig(config, "spearce"); + assertSame(TagOpt.NO_TAGS, rc.getTagOpt()); + } + + public void testSimpleAlwaysTags() throws Exception { + readConfig("[remote \"spearce\"]\n" + + "url = http://www.spearce.org/egit.git\n" + + "fetch = +refs/heads/*:refs/remotes/spearce/*\n" + + "tagopt = --tags\n"); + final RemoteConfig rc = new RemoteConfig(config, "spearce"); + assertSame(TagOpt.FETCH_TAGS, rc.getTagOpt()); + } + + public void testMirror() throws Exception { + readConfig("[remote \"spearce\"]\n" + + "url = http://www.spearce.org/egit.git\n" + + "fetch = +refs/heads/*:refs/heads/*\n" + + "fetch = refs/tags/*:refs/tags/*\n"); + + final RemoteConfig rc = new RemoteConfig(config, "spearce"); + final List allURIs = rc.getURIs(); + RefSpec spec; + + assertEquals("spearce", rc.getName()); + assertNotNull(allURIs); + assertNotNull(rc.getFetchRefSpecs()); + assertNotNull(rc.getPushRefSpecs()); + + assertEquals(1, allURIs.size()); + assertEquals("http://www.spearce.org/egit.git", allURIs.get(0)); + + assertEquals(2, rc.getFetchRefSpecs().size()); + + spec = rc.getFetchRefSpecs().get(0); + assertTrue(spec.isForceUpdate()); + assertTrue(spec.isWildcard()); + assertEquals("refs/heads/*", spec.getSource()); + assertEquals("refs/heads/*", spec.getDestination()); + + spec = rc.getFetchRefSpecs().get(1); + assertFalse(spec.isForceUpdate()); + assertTrue(spec.isWildcard()); + assertEquals("refs/tags/*", spec.getSource()); + assertEquals("refs/tags/*", spec.getDestination()); + + assertEquals(0, rc.getPushRefSpecs().size()); + } + + public void testBackup() throws Exception { + readConfig("[remote \"backup\"]\n" + + "url = http://www.spearce.org/egit.git\n" + + "url = user@repo.or.cz:/srv/git/egit.git\n" + + "push = +refs/heads/*:refs/heads/*\n" + + "push = refs/tags/*:refs/tags/*\n"); + + final RemoteConfig rc = new RemoteConfig(config, "backup"); + final List allURIs = rc.getURIs(); + RefSpec spec; + + assertEquals("backup", rc.getName()); + assertNotNull(allURIs); + assertNotNull(rc.getFetchRefSpecs()); + assertNotNull(rc.getPushRefSpecs()); + + assertEquals(2, allURIs.size()); + assertEquals("http://www.spearce.org/egit.git", allURIs.get(0)); + assertEquals("user@repo.or.cz:/srv/git/egit.git", allURIs.get(1)); + + assertEquals(0, rc.getFetchRefSpecs().size()); + + assertEquals(2, rc.getPushRefSpecs().size()); + spec = rc.getPushRefSpecs().get(0); + assertTrue(spec.isForceUpdate()); + assertTrue(spec.isWildcard()); + assertEquals("refs/heads/*", spec.getSource()); + assertEquals("refs/heads/*", spec.getDestination()); + + spec = rc.getPushRefSpecs().get(1); + assertFalse(spec.isForceUpdate()); + assertTrue(spec.isWildcard()); + assertEquals("refs/tags/*", spec.getSource()); + assertEquals("refs/tags/*", spec.getDestination()); + } + + public void testUploadPack() throws Exception { + readConfig("[remote \"example\"]\n" + + "url = user@example.com:egit.git\n" + + "fetch = +refs/heads/*:refs/remotes/example/*\n" + + "uploadpack = /path/to/git/git-upload-pack\n" + + "receivepack = /path/to/git/git-receive-pack\n"); + + final RemoteConfig rc = new RemoteConfig(config, "example"); + final List allURIs = rc.getURIs(); + RefSpec spec; + + assertEquals("example", rc.getName()); + assertNotNull(allURIs); + assertNotNull(rc.getFetchRefSpecs()); + assertNotNull(rc.getPushRefSpecs()); + + assertEquals(1, allURIs.size()); + assertEquals("user@example.com:egit.git", allURIs.get(0)); + + assertEquals(1, rc.getFetchRefSpecs().size()); + spec = rc.getFetchRefSpecs().get(0); + assertTrue(spec.isForceUpdate()); + assertTrue(spec.isWildcard()); + assertEquals("refs/heads/*", spec.getSource()); + assertEquals("refs/remotes/example/*", spec.getDestination()); + + assertEquals(0, rc.getPushRefSpecs().size()); + + assertEquals("/path/to/git/git-upload-pack", rc.getUploadPack()); + assertEquals("/path/to/git/git-receive-pack", rc.getReceivePack()); + } + + public void testUnknown() throws Exception { + readConfig(""); + + final RemoteConfig rc = new RemoteConfig(config, "backup"); + assertEquals(0, rc.getURIs().size()); + assertEquals(0, rc.getFetchRefSpecs().size()); + assertEquals(0, rc.getPushRefSpecs().size()); + assertEquals("git-upload-pack", rc.getUploadPack()); + assertEquals("git-receive-pack", rc.getReceivePack()); + } + + public void testAddURI() throws Exception { + readConfig(""); + + final URIish uri = new URIish("/some/dir"); + final RemoteConfig rc = new RemoteConfig(config, "backup"); + assertEquals(0, rc.getURIs().size()); + + assertTrue(rc.addURI(uri)); + assertEquals(1, rc.getURIs().size()); + assertSame(uri, rc.getURIs().get(0)); + + assertFalse(rc.addURI(new URIish(uri.toString()))); + assertEquals(1, rc.getURIs().size()); + } + + public void testRemoveFirstURI() throws Exception { + readConfig(""); + + final URIish a = new URIish("/some/dir"); + final URIish b = new URIish("/another/dir"); + final URIish c = new URIish("/more/dirs"); + final RemoteConfig rc = new RemoteConfig(config, "backup"); + assertTrue(rc.addURI(a)); + assertTrue(rc.addURI(b)); + assertTrue(rc.addURI(c)); + + assertEquals(3, rc.getURIs().size()); + assertSame(a, rc.getURIs().get(0)); + assertSame(b, rc.getURIs().get(1)); + assertSame(c, rc.getURIs().get(2)); + + assertTrue(rc.removeURI(a)); + assertEquals(2, rc.getURIs().size()); + assertSame(b, rc.getURIs().get(0)); + assertSame(c, rc.getURIs().get(1)); + } + + public void testRemoveMiddleURI() throws Exception { + readConfig(""); + + final URIish a = new URIish("/some/dir"); + final URIish b = new URIish("/another/dir"); + final URIish c = new URIish("/more/dirs"); + final RemoteConfig rc = new RemoteConfig(config, "backup"); + assertTrue(rc.addURI(a)); + assertTrue(rc.addURI(b)); + assertTrue(rc.addURI(c)); + + assertEquals(3, rc.getURIs().size()); + assertSame(a, rc.getURIs().get(0)); + assertSame(b, rc.getURIs().get(1)); + assertSame(c, rc.getURIs().get(2)); + + assertTrue(rc.removeURI(b)); + assertEquals(2, rc.getURIs().size()); + assertSame(a, rc.getURIs().get(0)); + assertSame(c, rc.getURIs().get(1)); + } + + public void testRemoveLastURI() throws Exception { + readConfig(""); + + final URIish a = new URIish("/some/dir"); + final URIish b = new URIish("/another/dir"); + final URIish c = new URIish("/more/dirs"); + final RemoteConfig rc = new RemoteConfig(config, "backup"); + assertTrue(rc.addURI(a)); + assertTrue(rc.addURI(b)); + assertTrue(rc.addURI(c)); + + assertEquals(3, rc.getURIs().size()); + assertSame(a, rc.getURIs().get(0)); + assertSame(b, rc.getURIs().get(1)); + assertSame(c, rc.getURIs().get(2)); + + assertTrue(rc.removeURI(c)); + assertEquals(2, rc.getURIs().size()); + assertSame(a, rc.getURIs().get(0)); + assertSame(b, rc.getURIs().get(1)); + } + + public void testRemoveOnlyURI() throws Exception { + readConfig(""); + + final URIish a = new URIish("/some/dir"); + final RemoteConfig rc = new RemoteConfig(config, "backup"); + assertTrue(rc.addURI(a)); + + assertEquals(1, rc.getURIs().size()); + assertSame(a, rc.getURIs().get(0)); + + assertTrue(rc.removeURI(a)); + assertEquals(0, rc.getURIs().size()); + } + + public void testCreateOrigin() throws Exception { + final RemoteConfig rc = new RemoteConfig(config, "origin"); + rc.addURI(new URIish("/some/dir")); + rc.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/" + + rc.getName() + "/*")); + rc.update(config); + checkConfig("[remote \"origin\"]\n" + "\turl = /some/dir\n" + + "\tfetch = +refs/heads/*:refs/remotes/origin/*\n"); + } + + public void testSaveAddURI() throws Exception { + readConfig("[remote \"spearce\"]\n" + + "url = http://www.spearce.org/egit.git\n" + + "fetch = +refs/heads/*:refs/remotes/spearce/*\n"); + + final RemoteConfig rc = new RemoteConfig(config, "spearce"); + rc.addURI(new URIish("/some/dir")); + assertEquals(2, rc.getURIs().size()); + rc.update(config); + checkConfig("[remote \"spearce\"]\n" + + "\turl = http://www.spearce.org/egit.git\n" + + "\turl = /some/dir\n" + + "\tfetch = +refs/heads/*:refs/remotes/spearce/*\n"); + } + + public void testSaveRemoveLastURI() throws Exception { + readConfig("[remote \"spearce\"]\n" + + "url = http://www.spearce.org/egit.git\n" + + "url = /some/dir\n" + + "fetch = +refs/heads/*:refs/remotes/spearce/*\n"); + + final RemoteConfig rc = new RemoteConfig(config, "spearce"); + assertEquals(2, rc.getURIs().size()); + rc.removeURI(new URIish("/some/dir")); + assertEquals(1, rc.getURIs().size()); + rc.update(config); + checkConfig("[remote \"spearce\"]\n" + + "\turl = http://www.spearce.org/egit.git\n" + + "\tfetch = +refs/heads/*:refs/remotes/spearce/*\n"); + } + + public void testSaveRemoveFirstURI() throws Exception { + readConfig("[remote \"spearce\"]\n" + + "url = http://www.spearce.org/egit.git\n" + + "url = /some/dir\n" + + "fetch = +refs/heads/*:refs/remotes/spearce/*\n"); + + final RemoteConfig rc = new RemoteConfig(config, "spearce"); + assertEquals(2, rc.getURIs().size()); + rc.removeURI(new URIish("http://www.spearce.org/egit.git")); + assertEquals(1, rc.getURIs().size()); + rc.update(config); + checkConfig("[remote \"spearce\"]\n" + "\turl = /some/dir\n" + + "\tfetch = +refs/heads/*:refs/remotes/spearce/*\n"); + } + + public void testSaveNoTags() throws Exception { + final RemoteConfig rc = new RemoteConfig(config, "origin"); + rc.addURI(new URIish("/some/dir")); + rc.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/" + + rc.getName() + "/*")); + rc.setTagOpt(TagOpt.NO_TAGS); + rc.update(config); + checkConfig("[remote \"origin\"]\n" + "\turl = /some/dir\n" + + "\tfetch = +refs/heads/*:refs/remotes/origin/*\n" + + "\ttagopt = --no-tags\n"); + } + + public void testSaveAllTags() throws Exception { + final RemoteConfig rc = new RemoteConfig(config, "origin"); + rc.addURI(new URIish("/some/dir")); + rc.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/" + + rc.getName() + "/*")); + rc.setTagOpt(TagOpt.FETCH_TAGS); + rc.update(config); + checkConfig("[remote \"origin\"]\n" + "\turl = /some/dir\n" + + "\tfetch = +refs/heads/*:refs/remotes/origin/*\n" + + "\ttagopt = --tags\n"); + } + + public void testSimpleTimeout() throws Exception { + readConfig("[remote \"spearce\"]\n" + + "url = http://www.spearce.org/egit.git\n" + + "fetch = +refs/heads/*:refs/remotes/spearce/*\n" + + "timeout = 12\n"); + final RemoteConfig rc = new RemoteConfig(config, "spearce"); + assertEquals(12, rc.getTimeout()); + } + + public void testSaveTimeout() throws Exception { + final RemoteConfig rc = new RemoteConfig(config, "origin"); + rc.addURI(new URIish("/some/dir")); + rc.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/" + + rc.getName() + "/*")); + rc.setTimeout(60); + rc.update(config); + checkConfig("[remote \"origin\"]\n" + "\turl = /some/dir\n" + + "\tfetch = +refs/heads/*:refs/remotes/origin/*\n" + + "\ttimeout = 60\n"); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.java new file mode 100644 index 000000000..3c79f138c --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/SideBandOutputStreamTest.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.transport; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import junit.framework.TestCase; + +import org.eclipse.jgit.lib.Constants; + +// Note, test vectors created with: +// +// perl -e 'printf "%4.4x%s\n", 4+length($ARGV[0]),$ARGV[0]' + +public class SideBandOutputStreamTest extends TestCase { + private ByteArrayOutputStream rawOut; + + private PacketLineOut pckOut; + + protected void setUp() throws Exception { + super.setUp(); + rawOut = new ByteArrayOutputStream(); + pckOut = new PacketLineOut(rawOut); + } + + public void testWrite_CH_DATA() throws IOException { + final SideBandOutputStream out; + out = new SideBandOutputStream(SideBandOutputStream.CH_DATA, pckOut); + out.write(new byte[] { 'a', 'b', 'c' }); + assertBuffer("0008\001abc"); + } + + public void testWrite_CH_PROGRESS() throws IOException { + final SideBandOutputStream out; + out = new SideBandOutputStream(SideBandOutputStream.CH_PROGRESS, pckOut); + out.write(new byte[] { 'a', 'b', 'c' }); + assertBuffer("0008\002abc"); + } + + public void testWrite_CH_ERROR() throws IOException { + final SideBandOutputStream out; + out = new SideBandOutputStream(SideBandOutputStream.CH_ERROR, pckOut); + out.write(new byte[] { 'a', 'b', 'c' }); + assertBuffer("0008\003abc"); + } + + public void testWrite_Small() throws IOException { + final SideBandOutputStream out; + out = new SideBandOutputStream(SideBandOutputStream.CH_DATA, pckOut); + out.write('a'); + out.write('b'); + out.write('c'); + assertBuffer("0006\001a0006\001b0006\001c"); + } + + public void testWrite_Large() throws IOException { + final int buflen = SideBandOutputStream.MAX_BUF + - SideBandOutputStream.HDR_SIZE; + final byte[] buf = new byte[buflen]; + for (int i = 0; i < buf.length; i++) { + buf[i] = (byte) i; + } + + final SideBandOutputStream out; + out = new SideBandOutputStream(SideBandOutputStream.CH_DATA, pckOut); + out.write(buf); + + final byte[] act = rawOut.toByteArray(); + final String explen = Integer.toString(buf.length + 5, 16); + assertEquals(5 + buf.length, act.length); + assertEquals(new String(act, 0, 4, "UTF-8"), explen); + assertEquals(1, act[4]); + for (int i = 0, j = 5; i < buf.length; i++, j++) { + assertEquals(buf[i], act[j]); + } + } + + public void testFlush() throws IOException { + final int[] flushCnt = new int[1]; + final OutputStream mockout = new OutputStream() { + @Override + public void write(int arg0) throws IOException { + fail("should not write"); + } + + @Override + public void flush() throws IOException { + flushCnt[0]++; + } + }; + + new SideBandOutputStream(SideBandOutputStream.CH_DATA, + new PacketLineOut(mockout)).flush(); + assertEquals(0, flushCnt[0]); + + new SideBandOutputStream(SideBandOutputStream.CH_ERROR, + new PacketLineOut(mockout)).flush(); + assertEquals(1, flushCnt[0]); + + new SideBandOutputStream(SideBandOutputStream.CH_PROGRESS, + new PacketLineOut(mockout)).flush(); + assertEquals(2, flushCnt[0]); + } + + private void assertBuffer(final String exp) throws IOException { + assertEquals(exp, new String(rawOut.toByteArray(), + Constants.CHARACTER_ENCODING)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java new file mode 100644 index 000000000..75e661bcf --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.eclipse.jgit.lib.RepositoryConfig; +import org.eclipse.jgit.lib.RepositoryTestCase; + +public class TransportTest extends RepositoryTestCase { + private Transport transport; + + private RemoteConfig remoteConfig; + + @Override + public void setUp() throws Exception { + super.setUp(); + final RepositoryConfig config = db.getConfig(); + remoteConfig = new RemoteConfig(config, "test"); + remoteConfig.addURI(new URIish("http://everyones.loves.git/u/2")); + transport = null; + } + + @Override + protected void tearDown() throws Exception { + if (transport != null) { + transport.close(); + transport = null; + } + super.tearDown(); + } + + /** + * Test RefSpec to RemoteRefUpdate conversion with simple RefSpec - no + * wildcard, no tracking ref in repo configuration. + * + * @throws IOException + */ + public void testFindRemoteRefUpdatesNoWildcardNoTracking() + throws IOException { + transport = Transport.open(db, remoteConfig); + final Collection result = transport + .findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec( + "refs/heads/master:refs/heads/x"))); + + assertEquals(1, result.size()); + final RemoteRefUpdate rru = result.iterator().next(); + assertNull(rru.getExpectedOldObjectId()); + assertFalse(rru.isForceUpdate()); + assertEquals("refs/heads/master", rru.getSrcRef()); + assertEquals(db.resolve("refs/heads/master"), rru.getNewObjectId()); + assertEquals("refs/heads/x", rru.getRemoteName()); + } + + /** + * Test RefSpec to RemoteRefUpdate conversion with no-destination RefSpec + * (destination should be set up for the same name as source). + * + * @throws IOException + */ + public void testFindRemoteRefUpdatesNoWildcardNoDestination() + throws IOException { + transport = Transport.open(db, remoteConfig); + final Collection result = transport + .findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec( + "+refs/heads/master"))); + + assertEquals(1, result.size()); + final RemoteRefUpdate rru = result.iterator().next(); + assertNull(rru.getExpectedOldObjectId()); + assertTrue(rru.isForceUpdate()); + assertEquals("refs/heads/master", rru.getSrcRef()); + assertEquals(db.resolve("refs/heads/master"), rru.getNewObjectId()); + assertEquals("refs/heads/master", rru.getRemoteName()); + } + + /** + * Test RefSpec to RemoteRefUpdate conversion with wildcard RefSpec. + * + * @throws IOException + */ + public void testFindRemoteRefUpdatesWildcardNoTracking() throws IOException { + transport = Transport.open(db, remoteConfig); + final Collection result = transport + .findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec( + "+refs/heads/*:refs/heads/test/*"))); + + assertEquals(12, result.size()); + boolean foundA = false; + boolean foundB = false; + for (final RemoteRefUpdate rru : result) { + if ("refs/heads/a".equals(rru.getSrcRef()) + && "refs/heads/test/a".equals(rru.getRemoteName())) + foundA = true; + if ("refs/heads/b".equals(rru.getSrcRef()) + && "refs/heads/test/b".equals(rru.getRemoteName())) + foundB = true; + } + assertTrue(foundA); + assertTrue(foundB); + } + + /** + * Test RefSpec to RemoteRefUpdate conversion for more than one RefSpecs + * handling. + * + * @throws IOException + */ + public void testFindRemoteRefUpdatesTwoRefSpecs() throws IOException { + transport = Transport.open(db, remoteConfig); + final RefSpec specA = new RefSpec("+refs/heads/a:refs/heads/b"); + final RefSpec specC = new RefSpec("+refs/heads/c:refs/heads/d"); + final Collection specs = Arrays.asList(specA, specC); + final Collection result = transport + .findRemoteRefUpdatesFor(specs); + + assertEquals(2, result.size()); + boolean foundA = false; + boolean foundC = false; + for (final RemoteRefUpdate rru : result) { + if ("refs/heads/a".equals(rru.getSrcRef()) + && "refs/heads/b".equals(rru.getRemoteName())) + foundA = true; + if ("refs/heads/c".equals(rru.getSrcRef()) + && "refs/heads/d".equals(rru.getRemoteName())) + foundC = true; + } + assertTrue(foundA); + assertTrue(foundC); + } + + /** + * Test RefSpec to RemoteRefUpdate conversion for tracking ref search. + * + * @throws IOException + */ + public void testFindRemoteRefUpdatesTrackingRef() throws IOException { + remoteConfig.addFetchRefSpec(new RefSpec( + "refs/heads/*:refs/remotes/test/*")); + transport = Transport.open(db, remoteConfig); + final Collection result = transport + .findRemoteRefUpdatesFor(Collections.nCopies(1, new RefSpec( + "+refs/heads/a:refs/heads/a"))); + + assertEquals(1, result.size()); + final TrackingRefUpdate tru = result.iterator().next() + .getTrackingRefUpdate(); + assertEquals("refs/remotes/test/a", tru.getLocalName()); + assertEquals("refs/heads/a", tru.getRemoteName()); + assertEquals(db.resolve("refs/heads/a"), tru.getNewObjectId()); + assertNull(tru.getOldObjectId()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java new file mode 100644 index 000000000..2598fdc1f --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 junit.framework.TestCase; + +public class URIishTest extends TestCase { + + public void testUnixFile() throws Exception { + final String str = "/home/m y"; + URIish u = new URIish(str); + assertNull(u.getScheme()); + assertFalse(u.isRemote()); + assertEquals(str, u.getPath()); + assertEquals(str, u.toString()); + assertEquals(u, new URIish(str)); + } + + public void testWindowsFile() throws Exception { + final String str = "D:/m y"; + URIish u = new URIish(str); + assertNull(u.getScheme()); + assertFalse(u.isRemote()); + assertEquals(str, u.getPath()); + assertEquals(str, u.toString()); + assertEquals(u, new URIish(str)); + } + + public void testWindowsFile2() throws Exception { + final String str = "D:\\m y"; + URIish u = new URIish(str); + assertNull(u.getScheme()); + assertFalse(u.isRemote()); + assertEquals("D:/m y", u.getPath()); + assertEquals("D:/m y", u.toString()); + assertEquals(u, new URIish(str)); + } + + public void testUNC() throws Exception { + final String str = "\\\\some\\place"; + URIish u = new URIish(str); + assertNull(u.getScheme()); + assertFalse(u.isRemote()); + assertEquals("//some/place", u.getPath()); + assertEquals("//some/place", u.toString()); + assertEquals(u, new URIish(str)); + } + + public void testFileProtoUnix() throws Exception { + final String str = "file:///home/m y"; + URIish u = new URIish(str); + assertEquals("file", u.getScheme()); + assertFalse(u.isRemote()); + assertEquals("/home/m y", u.getPath()); + assertEquals(str, u.toString()); + assertEquals(u, new URIish(str)); + } + + public void testFileProtoWindows() throws Exception { + final String str = "file:///D:/m y"; + URIish u = new URIish(str); + assertEquals("file", u.getScheme()); + assertFalse(u.isRemote()); + assertEquals("D:/m y", u.getPath()); + assertEquals(str, u.toString()); + assertEquals(u, new URIish(str)); + } + + public void testGitProtoUnix() throws Exception { + final String str = "git://example.com/home/m y"; + URIish u = new URIish(str); + assertEquals("git", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("example.com", u.getHost()); + assertEquals("/home/m y", u.getPath()); + assertEquals(str, u.toString()); + assertEquals(u, new URIish(str)); + } + + public void testGitProtoUnixPort() throws Exception { + final String str = "git://example.com:333/home/m y"; + URIish u = new URIish(str); + assertEquals("git", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("example.com", u.getHost()); + assertEquals("/home/m y", u.getPath()); + assertEquals(333, u.getPort()); + assertEquals(str, u.toString()); + assertEquals(u, new URIish(str)); + } + + public void testGitProtoWindowsPort() throws Exception { + final String str = "git://example.com:338/D:/m y"; + URIish u = new URIish(str); + assertEquals("git", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("D:/m y", u.getPath()); + assertEquals(338, u.getPort()); + assertEquals("example.com", u.getHost()); + assertEquals(str, u.toString()); + assertEquals(u, new URIish(str)); + } + + public void testGitProtoWindows() throws Exception { + final String str = "git://example.com/D:/m y"; + URIish u = new URIish(str); + assertEquals("git", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("D:/m y", u.getPath()); + assertEquals("example.com", u.getHost()); + assertEquals(-1, u.getPort()); + assertEquals(str, u.toString()); + assertEquals(u, new URIish(str)); + } + + public void testScpStyleWithoutUser() throws Exception { + final String str = "example.com:some/p ath"; + URIish u = new URIish(str); + assertNull(u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("some/p ath", u.getPath()); + assertEquals("example.com", u.getHost()); + assertEquals(-1, u.getPort()); + assertEquals(str, u.toString()); + assertEquals(u, new URIish(str)); + } + + public void testScpStyleWithUser() throws Exception { + final String str = "user@example.com:some/p ath"; + URIish u = new URIish(str); + assertNull(u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("some/p ath", u.getPath()); + assertEquals("user", u.getUser()); + assertEquals("example.com", u.getHost()); + assertEquals(-1, u.getPort()); + assertEquals(str, u.toString()); + assertEquals(u, new URIish(str)); + } + + public void testGitSshProto() throws Exception { + final String str = "git+ssh://example.com/some/p ath"; + URIish u = new URIish(str); + assertEquals("git+ssh", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("/some/p ath", u.getPath()); + assertEquals("example.com", u.getHost()); + assertEquals(-1, u.getPort()); + assertEquals(str, u.toString()); + assertEquals(u, new URIish(str)); + } + + public void testSshGitProto() throws Exception { + final String str = "ssh+git://example.com/some/p ath"; + URIish u = new URIish(str); + assertEquals("ssh+git", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("/some/p ath", u.getPath()); + assertEquals("example.com", u.getHost()); + assertEquals(-1, u.getPort()); + assertEquals(str, u.toString()); + assertEquals(u, new URIish(str)); + } + + public void testSshProto() throws Exception { + final String str = "ssh://example.com/some/p ath"; + URIish u = new URIish(str); + assertEquals("ssh", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("/some/p ath", u.getPath()); + assertEquals("example.com", u.getHost()); + assertEquals(-1, u.getPort()); + assertEquals(str, u.toString()); + assertEquals(u, new URIish(str)); + } + + public void testSshProtoWithUserAndPort() throws Exception { + final String str = "ssh://user@example.com:33/some/p ath"; + URIish u = new URIish(str); + assertEquals("ssh", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("/some/p ath", u.getPath()); + assertEquals("example.com", u.getHost()); + assertEquals("user", u.getUser()); + assertNull(u.getPass()); + assertEquals(33, u.getPort()); + assertEquals(str, u.toString()); + assertEquals(u, new URIish(str)); + } + + public void testSshProtoWithUserPassAndPort() throws Exception { + final String str = "ssh://user:pass@example.com:33/some/p ath"; + URIish u = new URIish(str); + assertEquals("ssh", u.getScheme()); + assertTrue(u.isRemote()); + assertEquals("/some/p ath", u.getPath()); + assertEquals("example.com", u.getHost()); + assertEquals("user", u.getUser()); + assertEquals("pass", u.getPass()); + assertEquals(33, u.getPort()); + assertEquals(str, u.toPrivateString()); + assertEquals(u.setPass(null).toPrivateString(), u.toString()); + assertEquals(u, new URIish(str)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java new file mode 100644 index 000000000..e96445a30 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2009, Tor Arne Vestbø + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 junit.framework.TestCase; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.Repository; + + +public class AbstractTreeIteratorTest extends TestCase { + private static String prefix(String path) { + final int s = path.lastIndexOf('/'); + return s > 0 ? path.substring(0, s) : ""; + } + + public class FakeTreeIterator extends WorkingTreeIterator { + public FakeTreeIterator(String pathName, FileMode fileMode) { + super(prefix(pathName)); + mode = fileMode.getBits(); + + final int s = pathName.lastIndexOf('/'); + final byte[] name = Constants.encode(pathName.substring(s + 1)); + ensurePathCapacity(pathOffset + name.length, pathOffset); + System.arraycopy(name, 0, path, pathOffset, name.length); + pathLen = pathOffset + name.length; + } + + @Override + public AbstractTreeIterator createSubtreeIterator(Repository repo) + throws IncorrectObjectTypeException, IOException { + return null; + } + } + + public void testPathCompare() throws Exception { + assertTrue(new FakeTreeIterator("a", FileMode.REGULAR_FILE).pathCompare( + new FakeTreeIterator("a", FileMode.TREE)) < 0); + + assertTrue(new FakeTreeIterator("a", FileMode.TREE).pathCompare( + new FakeTreeIterator("a", FileMode.REGULAR_FILE)) > 0); + + assertTrue(new FakeTreeIterator("a", FileMode.REGULAR_FILE).pathCompare( + new FakeTreeIterator("a", FileMode.REGULAR_FILE)) == 0); + + assertTrue(new FakeTreeIterator("a", FileMode.TREE).pathCompare( + new FakeTreeIterator("a", FileMode.TREE)) == 0); + } + + public void testGrowPath() throws Exception { + final FakeTreeIterator i = new FakeTreeIterator("ab", FileMode.TREE); + final byte[] origpath = i.path; + assertEquals(i.path[0], 'a'); + assertEquals(i.path[1], 'b'); + + i.growPath(2); + + assertNotSame(origpath, i.path); + assertEquals(origpath.length * 2, i.path.length); + assertEquals(i.path[0], 'a'); + assertEquals(i.path[1], 'b'); + } + + public void testEnsurePathCapacityFastCase() throws Exception { + final FakeTreeIterator i = new FakeTreeIterator("ab", FileMode.TREE); + final int want = 50; + final byte[] origpath = i.path; + assertEquals(i.path[0], 'a'); + assertEquals(i.path[1], 'b'); + assertTrue(want < i.path.length); + + i.ensurePathCapacity(want, 2); + + assertSame(origpath, i.path); + assertEquals(i.path[0], 'a'); + assertEquals(i.path[1], 'b'); + } + + public void testEnsurePathCapacityGrows() throws Exception { + final FakeTreeIterator i = new FakeTreeIterator("ab", FileMode.TREE); + final int want = 384; + final byte[] origpath = i.path; + assertEquals(i.path[0], 'a'); + assertEquals(i.path[1], 'b'); + assertTrue(i.path.length < want); + + i.ensurePathCapacity(want, 2); + + assertNotSame(origpath, i.path); + assertEquals(512, i.path.length); + assertEquals(i.path[0], 'a'); + assertEquals(i.path[1], 'b'); + } + + public void testEntryFileMode() { + for (FileMode m : new FileMode[] { FileMode.TREE, + FileMode.REGULAR_FILE, FileMode.EXECUTABLE_FILE, + FileMode.GITLINK, FileMode.SYMLINK }) { + final FakeTreeIterator i = new FakeTreeIterator("a", m); + assertEquals(m.getBits(), i.getEntryRawMode()); + assertSame(m, i.getEntryFileMode()); + } + } + + public void testEntryPath() { + FakeTreeIterator i = new FakeTreeIterator("a/b/cd", FileMode.TREE); + assertEquals("a/b/cd", i.getEntryPathString()); + assertEquals(2, i.getNameLength()); + byte[] b = new byte[3]; + b[0] = 0x0a; + i.getName(b, 1); + assertEquals(0x0a, b[0]); + assertEquals('c', b[1]); + assertEquals('d', b[2]); + } + + public void testCreateEmptyTreeIterator() { + FakeTreeIterator i = new FakeTreeIterator("a/b/cd", FileMode.TREE); + EmptyTreeIterator e = i.createEmptyTreeIterator(); + assertNotNull(e); + assertEquals(i.getEntryPathString() + "/", e.getEntryPathString()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java new file mode 100644 index 000000000..da25f8d63 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java @@ -0,0 +1,353 @@ +/* + * 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.treewalk; + +import java.io.ByteArrayOutputStream; + +import junit.framework.TestCase; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.util.RawParseUtils; + +public class CanonicalTreeParserTest extends TestCase { + private final CanonicalTreeParser ctp = new CanonicalTreeParser(); + + private final FileMode m644 = FileMode.REGULAR_FILE; + + private final FileMode mt = FileMode.TREE; + + private final ObjectId hash_a = ObjectId + .fromString("6b9c715d21d5486e59083fb6071566aa6ecd4d42"); + + private final ObjectId hash_foo = ObjectId + .fromString("a213e8e25bb2442326e86cbfb9ef56319f482869"); + + private final ObjectId hash_sometree = ObjectId + .fromString("daf4bdb0d7bb24319810fe0e73aa317663448c93"); + + private byte[] tree1; + + private byte[] tree2; + + private byte[] tree3; + + public void setUp() throws Exception { + super.setUp(); + + tree1 = mktree(entry(m644, "a", hash_a)); + tree2 = mktree(entry(m644, "a", hash_a), entry(m644, "foo", hash_foo)); + tree3 = mktree(entry(m644, "a", hash_a), entry(mt, "b_sometree", + hash_sometree), entry(m644, "foo", hash_foo)); + } + + private static byte[] mktree(final byte[]... data) throws Exception { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (final byte[] e : data) + out.write(e); + return out.toByteArray(); + } + + private static byte[] entry(final FileMode mode, final String name, + final ObjectId id) throws Exception { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + mode.copyTo(out); + out.write(' '); + out.write(Constants.encode(name)); + out.write(0); + id.copyRawTo(out); + return out.toByteArray(); + } + + private String path() { + return RawParseUtils.decode(Constants.CHARSET, ctp.path, + ctp.pathOffset, ctp.pathLen); + } + + public void testEmptyTree_AtEOF() throws Exception { + ctp.reset(new byte[0]); + assertTrue(ctp.eof()); + } + + public void testOneEntry_Forward() throws Exception { + ctp.reset(tree1); + + assertTrue(ctp.first()); + assertFalse(ctp.eof()); + assertEquals(m644.getBits(), ctp.mode); + assertEquals("a", path()); + assertEquals(hash_a, ctp.getEntryObjectId()); + + ctp.next(1); + assertFalse(ctp.first()); + assertTrue(ctp.eof()); + } + + public void testTwoEntries_ForwardOneAtATime() throws Exception { + ctp.reset(tree2); + + assertTrue(ctp.first()); + assertFalse(ctp.eof()); + assertEquals(m644.getBits(), ctp.mode); + assertEquals("a", path()); + assertEquals(hash_a, ctp.getEntryObjectId()); + + ctp.next(1); + assertFalse(ctp.eof()); + assertEquals(m644.getBits(), ctp.mode); + assertEquals("foo", path()); + assertEquals(hash_foo, ctp.getEntryObjectId()); + + ctp.next(1); + assertFalse(ctp.first()); + assertTrue(ctp.eof()); + } + + public void testOneEntry_Seek1IsEOF() throws Exception { + ctp.reset(tree1); + ctp.next(1); + assertTrue(ctp.eof()); + } + + public void testTwoEntries_Seek2IsEOF() throws Exception { + ctp.reset(tree2); + ctp.next(2); + assertTrue(ctp.eof()); + } + + public void testThreeEntries_Seek3IsEOF() throws Exception { + ctp.reset(tree3); + ctp.next(3); + assertTrue(ctp.eof()); + } + + public void testThreeEntries_Seek2() throws Exception { + ctp.reset(tree3); + + ctp.next(2); + assertFalse(ctp.eof()); + assertFalse(ctp.eof()); + assertEquals(m644.getBits(), ctp.mode); + assertEquals("foo", path()); + assertEquals(hash_foo, ctp.getEntryObjectId()); + + ctp.next(1); + assertTrue(ctp.eof()); + } + + public void testOneEntry_Backwards() throws Exception { + ctp.reset(tree1); + ctp.next(1); + assertFalse(ctp.first()); + assertTrue(ctp.eof()); + + ctp.back(1); + assertTrue(ctp.first()); + assertFalse(ctp.eof()); + assertEquals(m644.getBits(), ctp.mode); + assertEquals("a", path()); + assertEquals(hash_a, ctp.getEntryObjectId()); + } + + public void testTwoEntries_BackwardsOneAtATime() throws Exception { + ctp.reset(tree2); + ctp.next(2); + assertTrue(ctp.eof()); + + ctp.back(1); + assertFalse(ctp.eof()); + assertEquals(m644.getBits(), ctp.mode); + assertEquals("foo", path()); + assertEquals(hash_foo, ctp.getEntryObjectId()); + + ctp.back(1); + assertFalse(ctp.eof()); + assertEquals(m644.getBits(), ctp.mode); + assertEquals("a", path()); + assertEquals(hash_a, ctp.getEntryObjectId()); + } + + public void testTwoEntries_BackwardsTwo() throws Exception { + ctp.reset(tree2); + ctp.next(2); + assertTrue(ctp.eof()); + + ctp.back(2); + assertFalse(ctp.eof()); + assertEquals(m644.getBits(), ctp.mode); + assertEquals("a", path()); + assertEquals(hash_a, ctp.getEntryObjectId()); + + ctp.next(1); + assertFalse(ctp.eof()); + assertEquals(m644.getBits(), ctp.mode); + assertEquals("foo", path()); + assertEquals(hash_foo, ctp.getEntryObjectId()); + + ctp.next(1); + assertTrue(ctp.eof()); + } + + public void testThreeEntries_BackwardsTwo() throws Exception { + ctp.reset(tree3); + ctp.next(3); + assertTrue(ctp.eof()); + + ctp.back(2); + assertFalse(ctp.eof()); + assertEquals(mt.getBits(), ctp.mode); + assertEquals("b_sometree", path()); + assertEquals(hash_sometree, ctp.getEntryObjectId()); + + ctp.next(1); + assertFalse(ctp.eof()); + assertEquals(m644.getBits(), ctp.mode); + assertEquals("foo", path()); + assertEquals(hash_foo, ctp.getEntryObjectId()); + + ctp.next(1); + assertTrue(ctp.eof()); + } + + public void testBackwards_ConfusingPathName() throws Exception { + final String aVeryConfusingName = "confusing 644 entry 755 and others"; + ctp.reset(mktree(entry(m644, "a", hash_a), entry(mt, aVeryConfusingName, + hash_sometree), entry(m644, "foo", hash_foo))); + ctp.next(3); + assertTrue(ctp.eof()); + + ctp.back(2); + assertFalse(ctp.eof()); + assertEquals(mt.getBits(), ctp.mode); + assertEquals(aVeryConfusingName, path()); + assertEquals(hash_sometree, ctp.getEntryObjectId()); + + ctp.back(1); + assertFalse(ctp.eof()); + assertEquals(m644.getBits(), ctp.mode); + assertEquals("a", path()); + assertEquals(hash_a, ctp.getEntryObjectId()); + } + + public void testBackwords_Prebuilts1() throws Exception { + // What is interesting about this test is the ObjectId for the + // "darwin-x86" path entry ends in an octal digit (37 == '7'). + // Thus when scanning backwards we could over scan and consume + // part of the SHA-1, and miss the path terminator. + // + final ObjectId common = ObjectId + .fromString("af7bf97cb9bce3f60f1d651a0ef862e9447dd8bc"); + final ObjectId darwinx86 = ObjectId + .fromString("e927f7398240f78face99e1a738dac54ef738e37"); + final ObjectId linuxx86 = ObjectId + .fromString("ac08dd97120c7cb7d06e98cd5b152011183baf21"); + final ObjectId windows = ObjectId + .fromString("6c4c64c221a022bb973165192cca4812033479df"); + + ctp.reset(mktree(entry(mt, "common", common), entry(mt, "darwin-x86", + darwinx86), entry(mt, "linux-x86", linuxx86), entry(mt, + "windows", windows))); + ctp.next(3); + assertEquals("windows", ctp.getEntryPathString()); + assertSame(mt, ctp.getEntryFileMode()); + assertEquals(windows, ctp.getEntryObjectId()); + + ctp.back(1); + assertEquals("linux-x86", ctp.getEntryPathString()); + assertSame(mt, ctp.getEntryFileMode()); + assertEquals(linuxx86, ctp.getEntryObjectId()); + + ctp.next(1); + assertEquals("windows", ctp.getEntryPathString()); + assertSame(mt, ctp.getEntryFileMode()); + assertEquals(windows, ctp.getEntryObjectId()); + } + + public void testBackwords_Prebuilts2() throws Exception { + // What is interesting about this test is the ObjectId for the + // "darwin-x86" path entry ends in an octal digit (37 == '7'). + // Thus when scanning backwards we could over scan and consume + // part of the SHA-1, and miss the path terminator. + // + final ObjectId common = ObjectId + .fromString("af7bf97cb9bce3f60f1d651a0ef862e9447dd8bc"); + final ObjectId darwinx86 = ObjectId + .fromString("0000000000000000000000000000000000000037"); + final ObjectId linuxx86 = ObjectId + .fromString("ac08dd97120c7cb7d06e98cd5b152011183baf21"); + final ObjectId windows = ObjectId + .fromString("6c4c64c221a022bb973165192cca4812033479df"); + + ctp.reset(mktree(entry(mt, "common", common), entry(mt, "darwin-x86", + darwinx86), entry(mt, "linux-x86", linuxx86), entry(mt, + "windows", windows))); + ctp.next(3); + assertEquals("windows", ctp.getEntryPathString()); + assertSame(mt, ctp.getEntryFileMode()); + assertEquals(windows, ctp.getEntryObjectId()); + + ctp.back(1); + assertEquals("linux-x86", ctp.getEntryPathString()); + assertSame(mt, ctp.getEntryFileMode()); + assertEquals(linuxx86, ctp.getEntryObjectId()); + + ctp.next(1); + assertEquals("windows", ctp.getEntryPathString()); + assertSame(mt, ctp.getEntryFileMode()); + assertEquals(windows, ctp.getEntryObjectId()); + } + + public void testFreakingHugePathName() throws Exception { + final int n = AbstractTreeIterator.DEFAULT_PATH_SIZE * 4; + final StringBuilder b = new StringBuilder(n); + for (int i = 0; i < n; i++) + b.append('q'); + final String name = b.toString(); + ctp.reset(entry(m644, name, hash_a)); + assertFalse(ctp.eof()); + assertEquals(name, RawParseUtils.decode(Constants.CHARSET, ctp.path, + ctp.pathOffset, ctp.pathLen)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/EmptyTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/EmptyTreeIteratorTest.java new file mode 100644 index 000000000..111264b1c --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/EmptyTreeIteratorTest.java @@ -0,0 +1,112 @@ +/* + * 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.lib.ObjectId; +import org.eclipse.jgit.lib.RepositoryTestCase; + +public class EmptyTreeIteratorTest extends RepositoryTestCase { + public void testAtEOF() throws Exception { + final EmptyTreeIterator etp = new EmptyTreeIterator(); + assertTrue(etp.first()); + assertTrue(etp.eof()); + } + + public void testCreateSubtreeIterator() throws Exception { + final EmptyTreeIterator etp = new EmptyTreeIterator(); + final AbstractTreeIterator sub = etp.createSubtreeIterator(db); + assertNotNull(sub); + assertTrue(sub.first()); + assertTrue(sub.eof()); + assertTrue(sub instanceof EmptyTreeIterator); + } + + public void testEntryObjectId() throws Exception { + final EmptyTreeIterator etp = new EmptyTreeIterator(); + assertSame(ObjectId.zeroId(), etp.getEntryObjectId()); + assertNotNull(etp.idBuffer()); + assertEquals(0, etp.idOffset()); + assertEquals(ObjectId.zeroId(), ObjectId.fromRaw(etp.idBuffer())); + } + + public void testNextDoesNothing() throws Exception { + final EmptyTreeIterator etp = new EmptyTreeIterator(); + etp.next(1); + assertTrue(etp.first()); + assertTrue(etp.eof()); + assertEquals(ObjectId.zeroId(), ObjectId.fromRaw(etp.idBuffer())); + + etp.next(1); + assertTrue(etp.first()); + assertTrue(etp.eof()); + assertEquals(ObjectId.zeroId(), ObjectId.fromRaw(etp.idBuffer())); + } + + public void testBackDoesNothing() throws Exception { + final EmptyTreeIterator etp = new EmptyTreeIterator(); + etp.back(1); + assertTrue(etp.first()); + assertTrue(etp.eof()); + assertEquals(ObjectId.zeroId(), ObjectId.fromRaw(etp.idBuffer())); + + etp.back(1); + assertTrue(etp.first()); + assertTrue(etp.eof()); + assertEquals(ObjectId.zeroId(), ObjectId.fromRaw(etp.idBuffer())); + } + + public void testStopWalkCallsParent() throws Exception { + final boolean called[] = new boolean[1]; + assertFalse(called[0]); + + final EmptyTreeIterator parent = new EmptyTreeIterator() { + @Override + public void stopWalk() { + called[0] = true; + } + }; + parent.createSubtreeIterator(db).stopWalk(); + assertTrue(called[0]); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java new file mode 100644 index 000000000..081290310 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java @@ -0,0 +1,173 @@ +/* + * 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 java.io.File; +import java.security.MessageDigest; + +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.util.RawParseUtils; + +public class FileTreeIteratorTest extends RepositoryTestCase { + private final String[] paths = { "a,", "a,b", "a/b", "a0b" }; + + private long[] mtime; + + public void setUp() throws Exception { + super.setUp(); + + // We build the entries backwards so that on POSIX systems we + // are likely to get the entries in the trash directory in the + // opposite order of what they should be in for the iteration. + // This should stress the sorting code better than doing it in + // the correct order. + // + mtime = new long[paths.length]; + for (int i = paths.length - 1; i >= 0; i--) { + final String s = paths[i]; + writeTrashFile(s, s); + mtime[i] = new File(trash, s).lastModified(); + } + } + + public void testEmptyIfRootIsFile() throws Exception { + final File r = new File(trash, paths[0]); + assertTrue(r.isFile()); + final FileTreeIterator fti = new FileTreeIterator(r); + assertTrue(fti.first()); + assertTrue(fti.eof()); + } + + public void testEmptyIfRootDoesNotExist() throws Exception { + final File r = new File(trash, "not-existing-file"); + assertFalse(r.exists()); + final FileTreeIterator fti = new FileTreeIterator(r); + assertTrue(fti.first()); + assertTrue(fti.eof()); + } + + public void testEmptyIfRootIsEmpty() throws Exception { + final File r = new File(trash, "not-existing-file"); + assertFalse(r.exists()); + r.mkdir(); + assertTrue(r.isDirectory()); + + final FileTreeIterator fti = new FileTreeIterator(r); + assertTrue(fti.first()); + assertTrue(fti.eof()); + } + + public void testSimpleIterate() throws Exception { + final FileTreeIterator top = new FileTreeIterator(trash); + + assertTrue(top.first()); + assertFalse(top.eof()); + assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode); + assertEquals(paths[0], nameOf(top)); + assertEquals(paths[0].length(), top.getEntryLength()); + assertEquals(mtime[0], top.getEntryLastModified()); + + top.next(1); + assertFalse(top.first()); + assertFalse(top.eof()); + assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode); + assertEquals(paths[1], nameOf(top)); + assertEquals(paths[1].length(), top.getEntryLength()); + assertEquals(mtime[1], top.getEntryLastModified()); + + top.next(1); + assertFalse(top.first()); + assertFalse(top.eof()); + assertEquals(FileMode.TREE.getBits(), top.mode); + + final AbstractTreeIterator sub = top.createSubtreeIterator(db); + assertTrue(sub instanceof FileTreeIterator); + final FileTreeIterator subfti = (FileTreeIterator) sub; + assertTrue(sub.first()); + assertFalse(sub.eof()); + assertEquals(paths[2], nameOf(sub)); + assertEquals(paths[2].length(), subfti.getEntryLength()); + assertEquals(mtime[2], subfti.getEntryLastModified()); + + sub.next(1); + assertTrue(sub.eof()); + + top.next(1); + assertFalse(top.first()); + assertFalse(top.eof()); + assertEquals(FileMode.REGULAR_FILE.getBits(), top.mode); + assertEquals(paths[3], nameOf(top)); + assertEquals(paths[3].length(), top.getEntryLength()); + assertEquals(mtime[3], top.getEntryLastModified()); + + top.next(1); + assertTrue(top.eof()); + } + + public void testComputeFileObjectId() throws Exception { + final FileTreeIterator top = new FileTreeIterator(trash); + + final MessageDigest md = Constants.newMessageDigest(); + md.update(Constants.encodeASCII(Constants.TYPE_BLOB)); + md.update((byte) ' '); + md.update(Constants.encodeASCII(paths[0].length())); + md.update((byte) 0); + md.update(Constants.encode(paths[0])); + final ObjectId expect = ObjectId.fromRaw(md.digest()); + + assertEquals(expect, top.getEntryObjectId()); + + // Verify it was cached by removing the file and getting it again. + // + new File(trash, paths[0]).delete(); + assertEquals(expect, top.getEntryObjectId()); + } + + private static String nameOf(final AbstractTreeIterator i) { + return RawParseUtils.decode(Constants.CHARSET, i.path, 0, i.pathLen); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/NameConflictTreeWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/NameConflictTreeWalkTest.java new file mode 100644 index 000000000..35298b803 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/NameConflictTreeWalkTest.java @@ -0,0 +1,211 @@ +/* + * 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 java.io.ByteArrayInputStream; + +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.dircache.DirCacheIterator; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.RepositoryTestCase; + +public class NameConflictTreeWalkTest extends RepositoryTestCase { + private static final FileMode TREE = FileMode.TREE; + + private static final FileMode SYMLINK = FileMode.SYMLINK; + + private static final FileMode MISSING = FileMode.MISSING; + + private static final FileMode REGULAR_FILE = FileMode.REGULAR_FILE; + + private static final FileMode EXECUTABLE_FILE = FileMode.EXECUTABLE_FILE; + + public void testNoDF_NoGap() throws Exception { + final DirCache tree0 = DirCache.read(db); + final DirCache tree1 = DirCache.read(db); + { + final DirCacheBuilder b0 = tree0.builder(); + final DirCacheBuilder b1 = tree1.builder(); + + b0.add(makeEntry("a", REGULAR_FILE)); + b0.add(makeEntry("a.b", EXECUTABLE_FILE)); + b1.add(makeEntry("a/b", REGULAR_FILE)); + b0.add(makeEntry("a0b", SYMLINK)); + + b0.finish(); + b1.finish(); + assertEquals(3, tree0.getEntryCount()); + assertEquals(1, tree1.getEntryCount()); + } + + final TreeWalk tw = new TreeWalk(db); + tw.reset(); + tw.addTree(new DirCacheIterator(tree0)); + tw.addTree(new DirCacheIterator(tree1)); + + assertModes("a", REGULAR_FILE, MISSING, tw); + assertModes("a.b", EXECUTABLE_FILE, MISSING, tw); + assertModes("a", MISSING, TREE, tw); + tw.enterSubtree(); + assertModes("a/b", MISSING, REGULAR_FILE, tw); + assertModes("a0b", SYMLINK, MISSING, tw); + } + + public void testDF_NoGap() throws Exception { + final DirCache tree0 = DirCache.read(db); + final DirCache tree1 = DirCache.read(db); + { + final DirCacheBuilder b0 = tree0.builder(); + final DirCacheBuilder b1 = tree1.builder(); + + b0.add(makeEntry("a", REGULAR_FILE)); + b0.add(makeEntry("a.b", EXECUTABLE_FILE)); + b1.add(makeEntry("a/b", REGULAR_FILE)); + b0.add(makeEntry("a0b", SYMLINK)); + + b0.finish(); + b1.finish(); + assertEquals(3, tree0.getEntryCount()); + assertEquals(1, tree1.getEntryCount()); + } + + final NameConflictTreeWalk tw = new NameConflictTreeWalk(db); + tw.reset(); + tw.addTree(new DirCacheIterator(tree0)); + tw.addTree(new DirCacheIterator(tree1)); + + assertModes("a", REGULAR_FILE, TREE, tw); + assertTrue(tw.isSubtree()); + tw.enterSubtree(); + assertModes("a/b", MISSING, REGULAR_FILE, tw); + assertModes("a.b", EXECUTABLE_FILE, MISSING, tw); + assertModes("a0b", SYMLINK, MISSING, tw); + } + + public void testDF_GapByOne() throws Exception { + final DirCache tree0 = DirCache.read(db); + final DirCache tree1 = DirCache.read(db); + { + final DirCacheBuilder b0 = tree0.builder(); + final DirCacheBuilder b1 = tree1.builder(); + + b0.add(makeEntry("a", REGULAR_FILE)); + b0.add(makeEntry("a.b", EXECUTABLE_FILE)); + b1.add(makeEntry("a.b", EXECUTABLE_FILE)); + b1.add(makeEntry("a/b", REGULAR_FILE)); + b0.add(makeEntry("a0b", SYMLINK)); + + b0.finish(); + b1.finish(); + assertEquals(3, tree0.getEntryCount()); + assertEquals(2, tree1.getEntryCount()); + } + + final NameConflictTreeWalk tw = new NameConflictTreeWalk(db); + tw.reset(); + tw.addTree(new DirCacheIterator(tree0)); + tw.addTree(new DirCacheIterator(tree1)); + + assertModes("a", REGULAR_FILE, TREE, tw); + assertTrue(tw.isSubtree()); + tw.enterSubtree(); + assertModes("a/b", MISSING, REGULAR_FILE, tw); + assertModes("a.b", EXECUTABLE_FILE, EXECUTABLE_FILE, tw); + assertModes("a0b", SYMLINK, MISSING, tw); + } + + public void testDF_SkipsSeenSubtree() throws Exception { + final DirCache tree0 = DirCache.read(db); + final DirCache tree1 = DirCache.read(db); + { + final DirCacheBuilder b0 = tree0.builder(); + final DirCacheBuilder b1 = tree1.builder(); + + b0.add(makeEntry("a", REGULAR_FILE)); + b1.add(makeEntry("a.b", EXECUTABLE_FILE)); + b1.add(makeEntry("a/b", REGULAR_FILE)); + b0.add(makeEntry("a0b", SYMLINK)); + b1.add(makeEntry("a0b", SYMLINK)); + + b0.finish(); + b1.finish(); + assertEquals(2, tree0.getEntryCount()); + assertEquals(3, tree1.getEntryCount()); + } + + final NameConflictTreeWalk tw = new NameConflictTreeWalk(db); + tw.reset(); + tw.addTree(new DirCacheIterator(tree0)); + tw.addTree(new DirCacheIterator(tree1)); + + assertModes("a", REGULAR_FILE, TREE, tw); + assertTrue(tw.isSubtree()); + tw.enterSubtree(); + assertModes("a/b", MISSING, REGULAR_FILE, tw); + assertModes("a.b", MISSING, EXECUTABLE_FILE, tw); + assertModes("a0b", SYMLINK, SYMLINK, tw); + } + + private DirCacheEntry makeEntry(final String path, final FileMode mode) + throws Exception { + final byte[] pathBytes = Constants.encode(path); + final DirCacheEntry ent = new DirCacheEntry(path); + ent.setFileMode(mode); + ent.setObjectId(new ObjectWriter(db).computeBlobSha1(pathBytes.length, + new ByteArrayInputStream(pathBytes))); + return ent; + } + + private static void assertModes(final String path, final FileMode mode0, + final FileMode mode1, final TreeWalk tw) throws Exception { + assertTrue("has " + path, tw.next()); + assertEquals(path, tw.getPathString()); + assertEquals(mode0, tw.getFileMode(0)); + assertEquals(mode1, tw.getFileMode(1)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java new file mode 100644 index 000000000..d136b8f29 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/PostOrderTreeWalkTest.java @@ -0,0 +1,195 @@ +/* + * 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 java.io.ByteArrayInputStream; + +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.dircache.DirCacheIterator; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.RepositoryTestCase; + +import static org.eclipse.jgit.lib.FileMode.REGULAR_FILE; +import static org.eclipse.jgit.lib.FileMode.TREE; + +public class PostOrderTreeWalkTest extends RepositoryTestCase { + public void testInitialize_NoPostOrder() throws Exception { + final TreeWalk tw = new TreeWalk(db); + assertFalse(tw.isPostOrderTraversal()); + } + + public void testInitialize_TogglePostOrder() throws Exception { + final TreeWalk tw = new TreeWalk(db); + assertFalse(tw.isPostOrderTraversal()); + tw.setPostOrderTraversal(true); + assertTrue(tw.isPostOrderTraversal()); + tw.setPostOrderTraversal(false); + assertFalse(tw.isPostOrderTraversal()); + } + + public void testResetDoesNotAffectPostOrder() throws Exception { + final TreeWalk tw = new TreeWalk(db); + tw.setPostOrderTraversal(true); + assertTrue(tw.isPostOrderTraversal()); + tw.reset(); + assertTrue(tw.isPostOrderTraversal()); + + tw.setPostOrderTraversal(false); + assertFalse(tw.isPostOrderTraversal()); + tw.reset(); + assertFalse(tw.isPostOrderTraversal()); + } + + public void testNoPostOrder() throws Exception { + final DirCache tree = DirCache.read(db); + { + final DirCacheBuilder b = tree.builder(); + + b.add(makeFile("a")); + b.add(makeFile("b/c")); + b.add(makeFile("b/d")); + b.add(makeFile("q")); + + b.finish(); + assertEquals(4, tree.getEntryCount()); + } + + final TreeWalk tw = new TreeWalk(db); + tw.reset(); + tw.setPostOrderTraversal(false); + tw.addTree(new DirCacheIterator(tree)); + + assertModes("a", REGULAR_FILE, tw); + assertModes("b", TREE, tw); + assertTrue(tw.isSubtree()); + assertFalse(tw.isPostChildren()); + tw.enterSubtree(); + assertModes("b/c", REGULAR_FILE, tw); + assertModes("b/d", REGULAR_FILE, tw); + assertModes("q", REGULAR_FILE, tw); + } + + public void testWithPostOrder_EnterSubtree() throws Exception { + final DirCache tree = DirCache.read(db); + { + final DirCacheBuilder b = tree.builder(); + + b.add(makeFile("a")); + b.add(makeFile("b/c")); + b.add(makeFile("b/d")); + b.add(makeFile("q")); + + b.finish(); + assertEquals(4, tree.getEntryCount()); + } + + final TreeWalk tw = new TreeWalk(db); + tw.reset(); + tw.setPostOrderTraversal(true); + tw.addTree(new DirCacheIterator(tree)); + + assertModes("a", REGULAR_FILE, tw); + + assertModes("b", TREE, tw); + assertTrue(tw.isSubtree()); + assertFalse(tw.isPostChildren()); + tw.enterSubtree(); + assertModes("b/c", REGULAR_FILE, tw); + assertModes("b/d", REGULAR_FILE, tw); + + assertModes("b", TREE, tw); + assertTrue(tw.isSubtree()); + assertTrue(tw.isPostChildren()); + + assertModes("q", REGULAR_FILE, tw); + } + + public void testWithPostOrder_NoEnterSubtree() throws Exception { + final DirCache tree = DirCache.read(db); + { + final DirCacheBuilder b = tree.builder(); + + b.add(makeFile("a")); + b.add(makeFile("b/c")); + b.add(makeFile("b/d")); + b.add(makeFile("q")); + + b.finish(); + assertEquals(4, tree.getEntryCount()); + } + + final TreeWalk tw = new TreeWalk(db); + tw.reset(); + tw.setPostOrderTraversal(true); + tw.addTree(new DirCacheIterator(tree)); + + assertModes("a", REGULAR_FILE, tw); + + assertModes("b", TREE, tw); + assertTrue(tw.isSubtree()); + assertFalse(tw.isPostChildren()); + + assertModes("q", REGULAR_FILE, tw); + } + + private DirCacheEntry makeFile(final String path) throws Exception { + final byte[] pathBytes = Constants.encode(path); + final DirCacheEntry ent = new DirCacheEntry(path); + ent.setFileMode(REGULAR_FILE); + ent.setObjectId(new ObjectWriter(db).computeBlobSha1(pathBytes.length, + new ByteArrayInputStream(pathBytes))); + return ent; + } + + private static void assertModes(final String path, final FileMode mode0, + final TreeWalk tw) throws Exception { + assertTrue("has " + path, tw.next()); + assertEquals(path, tw.getPathString()); + assertEquals(mode0, tw.getFileMode(0)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java new file mode 100644 index 000000000..581683e34 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkBasicDiffTest.java @@ -0,0 +1,122 @@ +/* + * 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.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.lib.Tree; +import org.eclipse.jgit.treewalk.filter.TreeFilter; + +public class TreeWalkBasicDiffTest extends RepositoryTestCase { + public void testMissingSubtree_DetectFileAdded_FileModified() + throws Exception { + final ObjectWriter ow = new ObjectWriter(db); + final ObjectId aFileId = ow.writeBlob("a".getBytes()); + final ObjectId bFileId = ow.writeBlob("b".getBytes()); + final ObjectId cFileId1 = ow.writeBlob("c-1".getBytes()); + final ObjectId cFileId2 = ow.writeBlob("c-2".getBytes()); + + // Create sub-a/empty, sub-c/empty = hello. + final ObjectId oldTree; + { + final Tree root = new Tree(db); + { + final Tree subA = root.addTree("sub-a"); + subA.addFile("empty").setId(aFileId); + subA.setId(ow.writeTree(subA)); + } + { + final Tree subC = root.addTree("sub-c"); + subC.addFile("empty").setId(cFileId1); + subC.setId(ow.writeTree(subC)); + } + oldTree = ow.writeTree(root); + } + + // Create sub-a/empty, sub-b/empty, sub-c/empty. + final ObjectId newTree; + { + final Tree root = new Tree(db); + { + final Tree subA = root.addTree("sub-a"); + subA.addFile("empty").setId(aFileId); + subA.setId(ow.writeTree(subA)); + } + { + final Tree subB = root.addTree("sub-b"); + subB.addFile("empty").setId(bFileId); + subB.setId(ow.writeTree(subB)); + } + { + final Tree subC = root.addTree("sub-c"); + subC.addFile("empty").setId(cFileId2); + subC.setId(ow.writeTree(subC)); + } + newTree = ow.writeTree(root); + } + + final TreeWalk tw = new TreeWalk(db); + tw.reset(new ObjectId[] { oldTree, newTree }); + tw.setRecursive(true); + tw.setFilter(TreeFilter.ANY_DIFF); + + assertTrue(tw.next()); + assertEquals("sub-b/empty", tw.getPathString()); + assertEquals(FileMode.MISSING, tw.getFileMode(0)); + assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); + assertEquals(ObjectId.zeroId(), tw.getObjectId(0)); + assertEquals(bFileId, tw.getObjectId(1)); + + assertTrue(tw.next()); + assertEquals("sub-c/empty", tw.getPathString()); + assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(0)); + assertEquals(FileMode.REGULAR_FILE, tw.getFileMode(1)); + assertEquals(cFileId1, tw.getObjectId(0)); + assertEquals(cFileId2, tw.getObjectId(1)); + + assertFalse(tw.next()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/AlwaysCloneTreeFilter.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/AlwaysCloneTreeFilter.java new file mode 100644 index 000000000..8e2cca44a --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/AlwaysCloneTreeFilter.java @@ -0,0 +1,63 @@ +/* + * 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.filter; + +import org.eclipse.jgit.treewalk.TreeWalk; + +class AlwaysCloneTreeFilter extends TreeFilter { + @Override + public TreeFilter clone() { + return new AlwaysCloneTreeFilter(); + } + + @Override + public boolean include(final TreeWalk walker) { + return false; + } + + @Override + public boolean shouldBeRecursive() { + return false; + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/NotTreeFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/NotTreeFilterTest.java new file mode 100644 index 000000000..7c7ab3e6a --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/NotTreeFilterTest.java @@ -0,0 +1,91 @@ +/* + * 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.filter; + +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.treewalk.TreeWalk; + +public class NotTreeFilterTest extends RepositoryTestCase { + public void testWrap() throws Exception { + final TreeWalk tw = new TreeWalk(db); + final TreeFilter a = TreeFilter.ALL; + final TreeFilter n = NotTreeFilter.create(a); + assertNotNull(n); + assertTrue(a.include(tw)); + assertFalse(n.include(tw)); + } + + public void testNegateIsUnwrap() throws Exception { + final TreeFilter a = PathFilter.create("a/b"); + final TreeFilter n = NotTreeFilter.create(a); + assertSame(a, n.negate()); + } + + public void testShouldBeRecursive_ALL() throws Exception { + final TreeFilter a = TreeFilter.ALL; + final TreeFilter n = NotTreeFilter.create(a); + assertEquals(a.shouldBeRecursive(), n.shouldBeRecursive()); + } + + public void testShouldBeRecursive_PathFilter() throws Exception { + final TreeFilter a = PathFilter.create("a/b"); + assertTrue(a.shouldBeRecursive()); + final TreeFilter n = NotTreeFilter.create(a); + assertTrue(n.shouldBeRecursive()); + } + + public void testCloneIsDeepClone() throws Exception { + final TreeFilter a = new AlwaysCloneTreeFilter(); + assertNotSame(a, a.clone()); + final TreeFilter n = NotTreeFilter.create(a); + assertNotSame(n, n.clone()); + } + + public void testCloneIsSparseWhenPossible() throws Exception { + final TreeFilter a = TreeFilter.ALL; + assertSame(a, a.clone()); + final TreeFilter n = NotTreeFilter.create(a); + assertSame(n, n.clone()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTestCase.java new file mode 100644 index 000000000..1aaefc415 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathSuffixFilterTestCase.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.treewalk.filter; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.treewalk.TreeWalk; + +public class PathSuffixFilterTestCase extends RepositoryTestCase { + + public void testNonRecursiveFiltering() throws IOException { + final ObjectWriter ow = new ObjectWriter(db); + final ObjectId aSth = ow.writeBlob("a.sth".getBytes()); + final ObjectId aTxt = ow.writeBlob("a.txt".getBytes()); + final DirCache dc = DirCache.read(db); + final DirCacheBuilder builder = dc.builder(); + final DirCacheEntry aSthEntry = new DirCacheEntry("a.sth"); + aSthEntry.setFileMode(FileMode.REGULAR_FILE); + aSthEntry.setObjectId(aSth); + final DirCacheEntry aTxtEntry = new DirCacheEntry("a.txt"); + aTxtEntry.setFileMode(FileMode.REGULAR_FILE); + aTxtEntry.setObjectId(aTxt); + builder.add(aSthEntry); + builder.add(aTxtEntry); + builder.finish(); + final ObjectId treeId = dc.writeTree(ow); + + + final TreeWalk tw = new TreeWalk(db); + tw.setFilter(PathSuffixFilter.create(".txt")); + tw.addTree(treeId); + + List paths = new LinkedList(); + while (tw.next()) { + paths.add(tw.getPathString()); + } + + List expected = new LinkedList(); + expected.add("a.txt"); + + assertEquals(expected, paths); + } + + public void testRecursiveFiltering() throws IOException { + final ObjectWriter ow = new ObjectWriter(db); + final ObjectId aSth = ow.writeBlob("a.sth".getBytes()); + final ObjectId aTxt = ow.writeBlob("a.txt".getBytes()); + final ObjectId bSth = ow.writeBlob("b.sth".getBytes()); + final ObjectId bTxt = ow.writeBlob("b.txt".getBytes()); + final DirCache dc = DirCache.read(db); + final DirCacheBuilder builder = dc.builder(); + final DirCacheEntry aSthEntry = new DirCacheEntry("a.sth"); + aSthEntry.setFileMode(FileMode.REGULAR_FILE); + aSthEntry.setObjectId(aSth); + final DirCacheEntry aTxtEntry = new DirCacheEntry("a.txt"); + aTxtEntry.setFileMode(FileMode.REGULAR_FILE); + aTxtEntry.setObjectId(aTxt); + builder.add(aSthEntry); + builder.add(aTxtEntry); + final DirCacheEntry bSthEntry = new DirCacheEntry("sub/b.sth"); + bSthEntry.setFileMode(FileMode.REGULAR_FILE); + bSthEntry.setObjectId(bSth); + final DirCacheEntry bTxtEntry = new DirCacheEntry("sub/b.txt"); + bTxtEntry.setFileMode(FileMode.REGULAR_FILE); + bTxtEntry.setObjectId(bTxt); + builder.add(bSthEntry); + builder.add(bTxtEntry); + builder.finish(); + final ObjectId treeId = dc.writeTree(ow); + + + final TreeWalk tw = new TreeWalk(db); + tw.setRecursive(true); + tw.setFilter(PathSuffixFilter.create(".txt")); + tw.addTree(treeId); + + List paths = new LinkedList(); + while (tw.next()) { + paths.add(tw.getPathString()); + } + + List expected = new LinkedList(); + expected.add("a.txt"); + expected.add("sub/b.txt"); + + assertEquals(expected, paths); + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/TreeFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/TreeFilterTest.java new file mode 100644 index 000000000..12326eade --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/TreeFilterTest.java @@ -0,0 +1,80 @@ +/* + * 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.filter; + +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.treewalk.TreeWalk; + +public class TreeFilterTest extends RepositoryTestCase { + public void testALL_IncludesAnything() throws Exception { + final TreeWalk tw = new TreeWalk(db); + assertTrue(TreeFilter.ALL.include(tw)); + } + + public void testALL_ShouldNotBeRecursive() throws Exception { + assertFalse(TreeFilter.ALL.shouldBeRecursive()); + } + + public void testALL_IdentityClone() throws Exception { + assertSame(TreeFilter.ALL, TreeFilter.ALL.clone()); + } + + public void testNotALL_IncludesNothing() throws Exception { + final TreeWalk tw = new TreeWalk(db); + assertFalse(TreeFilter.ALL.negate().include(tw)); + } + + public void testANY_DIFF_IncludesSingleTreeCase() throws Exception { + final TreeWalk tw = new TreeWalk(db); + assertTrue(TreeFilter.ANY_DIFF.include(tw)); + } + + public void testANY_DIFF_ShouldNotBeRecursive() throws Exception { + assertFalse(TreeFilter.ANY_DIFF.shouldBeRecursive()); + } + + public void testANY_DIFF_IdentityClone() throws Exception { + assertSame(TreeFilter.ANY_DIFF, TreeFilter.ANY_DIFF.clone()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java new file mode 100644 index 000000000..ecabeeea5 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/IntListTest.java @@ -0,0 +1,162 @@ +/* + * 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 junit.framework.TestCase; + +public class IntListTest extends TestCase { + public void testEmpty_DefaultCapacity() { + final IntList i = new IntList(); + assertEquals(0, i.size()); + try { + i.get(0); + fail("Accepted 0 index on empty list"); + } catch (ArrayIndexOutOfBoundsException e) { + assertTrue(true); + } + } + + public void testEmpty_SpecificCapacity() { + final IntList i = new IntList(5); + assertEquals(0, i.size()); + try { + i.get(0); + fail("Accepted 0 index on empty list"); + } catch (ArrayIndexOutOfBoundsException e) { + assertTrue(true); + } + } + + public void testAdd_SmallGroup() { + final IntList i = new IntList(); + final int n = 5; + for (int v = 0; v < n; v++) + i.add(10 + v); + assertEquals(n, i.size()); + + for (int v = 0; v < n; v++) + assertEquals(10 + v, i.get(v)); + try { + i.get(n); + fail("Accepted out of bound index on list"); + } catch (ArrayIndexOutOfBoundsException e) { + assertTrue(true); + } + } + + public void testAdd_ZeroCapacity() { + final IntList i = new IntList(0); + assertEquals(0, i.size()); + i.add(1); + assertEquals(1, i.get(0)); + } + + public void testAdd_LargeGroup() { + final IntList i = new IntList(); + final int n = 500; + for (int v = 0; v < n; v++) + i.add(10 + v); + assertEquals(n, i.size()); + + for (int v = 0; v < n; v++) + assertEquals(10 + v, i.get(v)); + try { + i.get(n); + fail("Accepted out of bound index on list"); + } catch (ArrayIndexOutOfBoundsException e) { + assertTrue(true); + } + } + + public void testFillTo0() { + final IntList i = new IntList(); + i.fillTo(0, Integer.MIN_VALUE); + assertEquals(0, i.size()); + } + + public void testFillTo1() { + final IntList i = new IntList(); + i.fillTo(1, Integer.MIN_VALUE); + assertEquals(1, i.size()); + i.add(0); + assertEquals(Integer.MIN_VALUE, i.get(0)); + assertEquals(0, i.get(1)); + } + + public void testFillTo100() { + final IntList i = new IntList(); + i.fillTo(100, Integer.MIN_VALUE); + assertEquals(100, i.size()); + i.add(3); + assertEquals(Integer.MIN_VALUE, i.get(99)); + assertEquals(3, i.get(100)); + } + + public void testClear() { + final IntList i = new IntList(); + final int n = 5; + for (int v = 0; v < n; v++) + i.add(10 + v); + assertEquals(n, i.size()); + + i.clear(); + assertEquals(0, i.size()); + try { + i.get(0); + fail("Accepted 0 index on empty list"); + } catch (ArrayIndexOutOfBoundsException e) { + assertTrue(true); + } + } + + public void testToString() { + final IntList i = new IntList(); + i.add(1); + assertEquals("[1]", i.toString()); + i.add(13); + i.add(5); + assertEquals("[1, 13, 5]", i.toString()); + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/JGitTestUtil.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/JGitTestUtil.java new file mode 100644 index 000000000..37418417d --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/JGitTestUtil.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Imran M Yousuf + * Copyright (C) 2008, Jonas Fonseca + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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.URISyntaxException; +import java.net.URL; + +public abstract class JGitTestUtil { + public static final String CLASSPATH_TO_RESOURCES = "org/eclipse/jgit/test/resources/"; + + private JGitTestUtil() { + throw new UnsupportedOperationException(); + } + + public static File getTestResourceFile(final String fileName) { + if (fileName == null || fileName.length() <= 0) { + return null; + } + final URL url = cl().getResource(CLASSPATH_TO_RESOURCES + fileName); + if (url == null) { + // If URL is null then try to load it as it was being + // loaded previously + return new File("tst", fileName); + } + try { + return new File(url.toURI()); + } catch(URISyntaxException e) { + return new File(url.getPath()); + } + } + + private static ClassLoader cl() { + return JGitTestUtil.class.getClassLoader(); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java new file mode 100644 index 000000000..21fea9e9d --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/NBTest.java @@ -0,0 +1,334 @@ +/* + * 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 junit.framework.TestCase; + +public class NBTest extends TestCase { + public void testCompareUInt32() { + assertTrue(NB.compareUInt32(0, 0) == 0); + assertTrue(NB.compareUInt32(1, 0) > 0); + assertTrue(NB.compareUInt32(0, 1) < 0); + assertTrue(NB.compareUInt32(-1, 0) > 0); + assertTrue(NB.compareUInt32(0, -1) < 0); + assertTrue(NB.compareUInt32(-1, 1) > 0); + assertTrue(NB.compareUInt32(1, -1) < 0); + } + + public void testDecodeUInt16() { + assertEquals(0, NB.decodeUInt16(b(0, 0), 0)); + assertEquals(0, NB.decodeUInt16(padb(3, 0, 0), 3)); + + assertEquals(3, NB.decodeUInt16(b(0, 3), 0)); + assertEquals(3, NB.decodeUInt16(padb(3, 0, 3), 3)); + + assertEquals(0xde03, NB.decodeUInt16(b(0xde, 3), 0)); + assertEquals(0xde03, NB.decodeUInt16(padb(3, 0xde, 3), 3)); + + assertEquals(0x03de, NB.decodeUInt16(b(3, 0xde), 0)); + assertEquals(0x03de, NB.decodeUInt16(padb(3, 3, 0xde), 3)); + + assertEquals(0xffff, NB.decodeUInt16(b(0xff, 0xff), 0)); + assertEquals(0xffff, NB.decodeUInt16(padb(3, 0xff, 0xff), 3)); + } + + public void testDecodeInt32() { + assertEquals(0, NB.decodeInt32(b(0, 0, 0, 0), 0)); + assertEquals(0, NB.decodeInt32(padb(3, 0, 0, 0, 0), 3)); + + assertEquals(3, NB.decodeInt32(b(0, 0, 0, 3), 0)); + assertEquals(3, NB.decodeInt32(padb(3, 0, 0, 0, 3), 3)); + + assertEquals(0xdeadbeef, NB.decodeInt32(b(0xde, 0xad, 0xbe, 0xef), 0)); + assertEquals(0xdeadbeef, NB.decodeInt32( + padb(3, 0xde, 0xad, 0xbe, 0xef), 3)); + + assertEquals(0x0310adef, NB.decodeInt32(b(0x03, 0x10, 0xad, 0xef), 0)); + assertEquals(0x0310adef, NB.decodeInt32( + padb(3, 0x03, 0x10, 0xad, 0xef), 3)); + + assertEquals(0xffffffff, NB.decodeInt32(b(0xff, 0xff, 0xff, 0xff), 0)); + assertEquals(0xffffffff, NB.decodeInt32( + padb(3, 0xff, 0xff, 0xff, 0xff), 3)); + } + + public void testDecodeUInt32() { + assertEquals(0L, NB.decodeUInt32(b(0, 0, 0, 0), 0)); + assertEquals(0L, NB.decodeUInt32(padb(3, 0, 0, 0, 0), 3)); + + assertEquals(3L, NB.decodeUInt32(b(0, 0, 0, 3), 0)); + assertEquals(3L, NB.decodeUInt32(padb(3, 0, 0, 0, 3), 3)); + + assertEquals(0xdeadbeefL, NB.decodeUInt32(b(0xde, 0xad, 0xbe, 0xef), 0)); + assertEquals(0xdeadbeefL, NB.decodeUInt32(padb(3, 0xde, 0xad, 0xbe, + 0xef), 3)); + + assertEquals(0x0310adefL, NB.decodeUInt32(b(0x03, 0x10, 0xad, 0xef), 0)); + assertEquals(0x0310adefL, NB.decodeUInt32(padb(3, 0x03, 0x10, 0xad, + 0xef), 3)); + + assertEquals(0xffffffffL, NB.decodeUInt32(b(0xff, 0xff, 0xff, 0xff), 0)); + assertEquals(0xffffffffL, NB.decodeUInt32(padb(3, 0xff, 0xff, 0xff, + 0xff), 3)); + } + + public void testDecodeUInt64() { + assertEquals(0L, NB.decodeUInt64(b(0, 0, 0, 0, 0, 0, 0, 0), 0)); + assertEquals(0L, NB.decodeUInt64(padb(3, 0, 0, 0, 0, 0, 0, 0, 0), 3)); + + assertEquals(3L, NB.decodeUInt64(b(0, 0, 0, 0, 0, 0, 0, 3), 0)); + assertEquals(3L, NB.decodeUInt64(padb(3, 0, 0, 0, 0, 0, 0, 0, 3), 3)); + + assertEquals(0xdeadbeefL, NB.decodeUInt64(b(0, 0, 0, 0, 0xde, 0xad, + 0xbe, 0xef), 0)); + assertEquals(0xdeadbeefL, NB.decodeUInt64(padb(3, 0, 0, 0, 0, 0xde, + 0xad, 0xbe, 0xef), 3)); + + assertEquals(0x0310adefL, NB.decodeUInt64(b(0, 0, 0, 0, 0x03, 0x10, + 0xad, 0xef), 0)); + assertEquals(0x0310adefL, NB.decodeUInt64(padb(3, 0, 0, 0, 0, 0x03, + 0x10, 0xad, 0xef), 3)); + + assertEquals(0xc0ffee78deadbeefL, NB.decodeUInt64(b(0xc0, 0xff, 0xee, + 0x78, 0xde, 0xad, 0xbe, 0xef), 0)); + assertEquals(0xc0ffee78deadbeefL, NB.decodeUInt64(padb(3, 0xc0, 0xff, + 0xee, 0x78, 0xde, 0xad, 0xbe, 0xef), 3)); + + assertEquals(0x00000000ffffffffL, NB.decodeUInt64(b(0, 0, 0, 0, 0xff, + 0xff, 0xff, 0xff), 0)); + assertEquals(0x00000000ffffffffL, NB.decodeUInt64(padb(3, 0, 0, 0, 0, + 0xff, 0xff, 0xff, 0xff), 3)); + assertEquals(0xffffffffffffffffL, NB.decodeUInt64(b(0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff), 0)); + assertEquals(0xffffffffffffffffL, NB.decodeUInt64(padb(3, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff), 3)); + } + + public void testEncodeInt16() { + final byte[] out = new byte[16]; + + prepareOutput(out); + NB.encodeInt16(out, 0, 0); + assertOutput(b(0, 0), out, 0); + + prepareOutput(out); + NB.encodeInt16(out, 3, 0); + assertOutput(b(0, 0), out, 3); + + prepareOutput(out); + NB.encodeInt16(out, 0, 3); + assertOutput(b(0, 3), out, 0); + + prepareOutput(out); + NB.encodeInt16(out, 3, 3); + assertOutput(b(0, 3), out, 3); + + prepareOutput(out); + NB.encodeInt16(out, 0, 0xdeac); + assertOutput(b(0xde, 0xac), out, 0); + + prepareOutput(out); + NB.encodeInt16(out, 3, 0xdeac); + assertOutput(b(0xde, 0xac), out, 3); + + prepareOutput(out); + NB.encodeInt16(out, 3, -1); + assertOutput(b(0xff, 0xff), out, 3); + } + + public void testEncodeInt32() { + final byte[] out = new byte[16]; + + prepareOutput(out); + NB.encodeInt32(out, 0, 0); + assertOutput(b(0, 0, 0, 0), out, 0); + + prepareOutput(out); + NB.encodeInt32(out, 3, 0); + assertOutput(b(0, 0, 0, 0), out, 3); + + prepareOutput(out); + NB.encodeInt32(out, 0, 3); + assertOutput(b(0, 0, 0, 3), out, 0); + + prepareOutput(out); + NB.encodeInt32(out, 3, 3); + assertOutput(b(0, 0, 0, 3), out, 3); + + prepareOutput(out); + NB.encodeInt32(out, 0, 0xdeac); + assertOutput(b(0, 0, 0xde, 0xac), out, 0); + + prepareOutput(out); + NB.encodeInt32(out, 3, 0xdeac); + assertOutput(b(0, 0, 0xde, 0xac), out, 3); + + prepareOutput(out); + NB.encodeInt32(out, 0, 0xdeac9853); + assertOutput(b(0xde, 0xac, 0x98, 0x53), out, 0); + + prepareOutput(out); + NB.encodeInt32(out, 3, 0xdeac9853); + assertOutput(b(0xde, 0xac, 0x98, 0x53), out, 3); + + prepareOutput(out); + NB.encodeInt32(out, 3, -1); + assertOutput(b(0xff, 0xff, 0xff, 0xff), out, 3); + } + + public void testEncodeInt64() { + final byte[] out = new byte[16]; + + prepareOutput(out); + NB.encodeInt64(out, 0, 0L); + assertOutput(b(0, 0, 0, 0, 0, 0, 0, 0), out, 0); + + prepareOutput(out); + NB.encodeInt64(out, 3, 0L); + assertOutput(b(0, 0, 0, 0, 0, 0, 0, 0), out, 3); + + prepareOutput(out); + NB.encodeInt64(out, 0, 3L); + assertOutput(b(0, 0, 0, 0, 0, 0, 0, 3), out, 0); + + prepareOutput(out); + NB.encodeInt64(out, 3, 3L); + assertOutput(b(0, 0, 0, 0, 0, 0, 0, 3), out, 3); + + prepareOutput(out); + NB.encodeInt64(out, 0, 0xdeacL); + assertOutput(b(0, 0, 0, 0, 0, 0, 0xde, 0xac), out, 0); + + prepareOutput(out); + NB.encodeInt64(out, 3, 0xdeacL); + assertOutput(b(0, 0, 0, 0, 0, 0, 0xde, 0xac), out, 3); + + prepareOutput(out); + NB.encodeInt64(out, 0, 0xdeac9853L); + assertOutput(b(0, 0, 0, 0, 0xde, 0xac, 0x98, 0x53), out, 0); + + prepareOutput(out); + NB.encodeInt64(out, 3, 0xdeac9853L); + assertOutput(b(0, 0, 0, 0, 0xde, 0xac, 0x98, 0x53), out, 3); + + prepareOutput(out); + NB.encodeInt64(out, 0, 0xac431242deac9853L); + assertOutput(b(0xac, 0x43, 0x12, 0x42, 0xde, 0xac, 0x98, 0x53), out, 0); + + prepareOutput(out); + NB.encodeInt64(out, 3, 0xac431242deac9853L); + assertOutput(b(0xac, 0x43, 0x12, 0x42, 0xde, 0xac, 0x98, 0x53), out, 3); + + prepareOutput(out); + NB.encodeInt64(out, 3, -1L); + assertOutput(b(0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff), out, 3); + } + + private static void prepareOutput(final byte[] buf) { + for (int i = 0; i < buf.length; i++) + buf[i] = (byte) (0x77 + i); + } + + private static void assertOutput(final byte[] expect, final byte[] buf, + final int offset) { + for (int i = 0; i < offset; i++) + assertEquals((byte) (0x77 + i), buf[i]); + for (int i = 0; i < expect.length; i++) + assertEquals(expect[i], buf[offset + i]); + for (int i = offset + expect.length; i < buf.length; i++) + assertEquals((byte) (0x77 + i), buf[i]); + } + + private static byte[] b(final int a, final int b) { + return new byte[] { (byte) a, (byte) b }; + } + + private static byte[] padb(final int len, final int a, final int b) { + final byte[] r = new byte[len + 2]; + for (int i = 0; i < len; i++) + r[i] = (byte) 0xaf; + r[len] = (byte) a; + r[len + 1] = (byte) b; + return r; + } + + private static byte[] b(final int a, final int b, final int c, final int d) { + return new byte[] { (byte) a, (byte) b, (byte) c, (byte) d }; + } + + private static byte[] padb(final int len, final int a, final int b, + final int c, final int d) { + final byte[] r = new byte[len + 4]; + for (int i = 0; i < len; i++) + r[i] = (byte) 0xaf; + r[len] = (byte) a; + r[len + 1] = (byte) b; + r[len + 2] = (byte) c; + r[len + 3] = (byte) d; + return r; + } + + private static byte[] b(final int a, final int b, final int c, final int d, + final int e, final int f, final int g, final int h) { + return new byte[] { (byte) a, (byte) b, (byte) c, (byte) d, (byte) e, + (byte) f, (byte) g, (byte) h }; + } + + private static byte[] padb(final int len, final int a, final int b, + final int c, final int d, final int e, final int f, final int g, + final int h) { + final byte[] r = new byte[len + 8]; + for (int i = 0; i < len; i++) + r[i] = (byte) 0xaf; + r[len] = (byte) a; + r[len + 1] = (byte) b; + r[len + 2] = (byte) c; + r[len + 3] = (byte) d; + r[len + 4] = (byte) e; + r[len + 5] = (byte) f; + r[len + 6] = (byte) g; + r[len + 7] = (byte) h; + return r; + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringBourneStyleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringBourneStyleTest.java new file mode 100644 index 000000000..b9a721a46 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringBourneStyleTest.java @@ -0,0 +1,117 @@ +/* + * 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 static org.eclipse.jgit.util.QuotedString.BOURNE; +import junit.framework.TestCase; + +import org.eclipse.jgit.lib.Constants; + +public class QuotedStringBourneStyleTest extends TestCase { + private static void assertQuote(final String in, final String exp) { + final String r = BOURNE.quote(in); + assertNotSame(in, r); + assertFalse(in.equals(r)); + assertEquals('\'' + exp + '\'', r); + } + + private static void assertDequote(final String exp, final String in) { + final byte[] b = Constants.encode('\'' + in + '\''); + final String r = BOURNE.dequote(b, 0, b.length); + assertEquals(exp, r); + } + + public void testQuote_Empty() { + assertEquals("''", BOURNE.quote("")); + } + + public void testDequote_Empty1() { + assertEquals("", BOURNE.dequote(new byte[0], 0, 0)); + } + + public void testDequote_Empty2() { + assertEquals("", BOURNE.dequote(new byte[] { '\'', '\'' }, 0, 2)); + } + + public void testDequote_SoleSq() { + assertEquals("", BOURNE.dequote(new byte[] { '\'' }, 0, 1)); + } + + public void testQuote_BareA() { + assertQuote("a", "a"); + } + + public void testDequote_BareA() { + final String in = "a"; + final byte[] b = Constants.encode(in); + assertEquals(in, BOURNE.dequote(b, 0, b.length)); + } + + public void testDequote_BareABCZ_OnlyBC() { + final String in = "abcz"; + final byte[] b = Constants.encode(in); + final int p = in.indexOf('b'); + assertEquals("bc", BOURNE.dequote(b, p, p + 2)); + } + + public void testDequote_LoneBackslash() { + assertDequote("\\", "\\"); + } + + public void testQuote_NamedEscapes() { + assertQuote("'", "'\\''"); + assertQuote("!", "'\\!'"); + + assertQuote("a'b", "a'\\''b"); + assertQuote("a!b", "a'\\!'b"); + } + + public void testDequote_NamedEscapes() { + assertDequote("'", "'\\''"); + assertDequote("!", "'\\!'"); + + assertDequote("a'b", "a'\\''b"); + assertDequote("a!b", "a'\\!'b"); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringBourneUserPathStyleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringBourneUserPathStyleTest.java new file mode 100644 index 000000000..69201249c --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringBourneUserPathStyleTest.java @@ -0,0 +1,136 @@ +/* + * 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 static org.eclipse.jgit.util.QuotedString.BOURNE_USER_PATH; +import junit.framework.TestCase; + +import org.eclipse.jgit.lib.Constants; + +public class QuotedStringBourneUserPathStyleTest extends TestCase { + private static void assertQuote(final String in, final String exp) { + final String r = BOURNE_USER_PATH.quote(in); + assertNotSame(in, r); + assertFalse(in.equals(r)); + assertEquals('\'' + exp + '\'', r); + } + + private static void assertDequote(final String exp, final String in) { + final byte[] b = Constants.encode('\'' + in + '\''); + final String r = BOURNE_USER_PATH.dequote(b, 0, b.length); + assertEquals(exp, r); + } + + public void testQuote_Empty() { + assertEquals("''", BOURNE_USER_PATH.quote("")); + } + + public void testDequote_Empty1() { + assertEquals("", BOURNE_USER_PATH.dequote(new byte[0], 0, 0)); + } + + public void testDequote_Empty2() { + assertEquals("", BOURNE_USER_PATH.dequote(new byte[] { '\'', '\'' }, 0, + 2)); + } + + public void testDequote_SoleSq() { + assertEquals("", BOURNE_USER_PATH.dequote(new byte[] { '\'' }, 0, 1)); + } + + public void testQuote_BareA() { + assertQuote("a", "a"); + } + + public void testDequote_BareA() { + final String in = "a"; + final byte[] b = Constants.encode(in); + assertEquals(in, BOURNE_USER_PATH.dequote(b, 0, b.length)); + } + + public void testDequote_BareABCZ_OnlyBC() { + final String in = "abcz"; + final byte[] b = Constants.encode(in); + final int p = in.indexOf('b'); + assertEquals("bc", BOURNE_USER_PATH.dequote(b, p, p + 2)); + } + + public void testDequote_LoneBackslash() { + assertDequote("\\", "\\"); + } + + public void testQuote_NamedEscapes() { + assertQuote("'", "'\\''"); + assertQuote("!", "'\\!'"); + + assertQuote("a'b", "a'\\''b"); + assertQuote("a!b", "a'\\!'b"); + } + + public void testDequote_NamedEscapes() { + assertDequote("'", "'\\''"); + assertDequote("!", "'\\!'"); + + assertDequote("a'b", "a'\\''b"); + assertDequote("a!b", "a'\\!'b"); + } + + public void testQuote_User() { + assertEquals("~foo/", BOURNE_USER_PATH.quote("~foo")); + assertEquals("~foo/", BOURNE_USER_PATH.quote("~foo/")); + assertEquals("~/", BOURNE_USER_PATH.quote("~/")); + + assertEquals("~foo/'a'", BOURNE_USER_PATH.quote("~foo/a")); + assertEquals("~/'a'", BOURNE_USER_PATH.quote("~/a")); + } + + public void testDequote_User() { + assertEquals("~foo", BOURNE_USER_PATH.dequote("~foo")); + assertEquals("~foo/", BOURNE_USER_PATH.dequote("~foo/")); + assertEquals("~/", BOURNE_USER_PATH.dequote("~/")); + + assertEquals("~foo/a", BOURNE_USER_PATH.dequote("~foo/'a'")); + assertEquals("~/a", BOURNE_USER_PATH.dequote("~/'a'")); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringGitPathStyleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringGitPathStyleTest.java new file mode 100644 index 000000000..4a161fa01 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/QuotedStringGitPathStyleTest.java @@ -0,0 +1,187 @@ +/* + * 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 static org.eclipse.jgit.util.QuotedString.GIT_PATH; + +import java.io.UnsupportedEncodingException; + +import junit.framework.TestCase; + +import org.eclipse.jgit.lib.Constants; + +public class QuotedStringGitPathStyleTest extends TestCase { + private static void assertQuote(final String exp, final String in) { + final String r = GIT_PATH.quote(in); + assertNotSame(in, r); + assertFalse(in.equals(r)); + assertEquals('"' + exp + '"', r); + } + + private static void assertDequote(final String exp, final String in) { + final byte[] b; + try { + b = ('"' + in + '"').getBytes("ISO-8859-1"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + final String r = GIT_PATH.dequote(b, 0, b.length); + assertEquals(exp, r); + } + + public void testQuote_Empty() { + assertEquals("\"\"", GIT_PATH.quote("")); + } + + public void testDequote_Empty1() { + assertEquals("", GIT_PATH.dequote(new byte[0], 0, 0)); + } + + public void testDequote_Empty2() { + assertEquals("", GIT_PATH.dequote(new byte[] { '"', '"' }, 0, 2)); + } + + public void testDequote_SoleDq() { + assertEquals("\"", GIT_PATH.dequote(new byte[] { '"' }, 0, 1)); + } + + public void testQuote_BareA() { + final String in = "a"; + assertSame(in, GIT_PATH.quote(in)); + } + + public void testDequote_BareA() { + final String in = "a"; + final byte[] b = Constants.encode(in); + assertEquals(in, GIT_PATH.dequote(b, 0, b.length)); + } + + public void testDequote_BareABCZ_OnlyBC() { + final String in = "abcz"; + final byte[] b = Constants.encode(in); + final int p = in.indexOf('b'); + assertEquals("bc", GIT_PATH.dequote(b, p, p + 2)); + } + + public void testDequote_LoneBackslash() { + assertDequote("\\", "\\"); + } + + public void testQuote_NamedEscapes() { + assertQuote("\\a", "\u0007"); + assertQuote("\\b", "\b"); + assertQuote("\\f", "\f"); + assertQuote("\\n", "\n"); + assertQuote("\\r", "\r"); + assertQuote("\\t", "\t"); + assertQuote("\\v", "\u000B"); + assertQuote("\\\\", "\\"); + assertQuote("\\\"", "\""); + } + + public void testDequote_NamedEscapes() { + assertDequote("\u0007", "\\a"); + assertDequote("\b", "\\b"); + assertDequote("\f", "\\f"); + assertDequote("\n", "\\n"); + assertDequote("\r", "\\r"); + assertDequote("\t", "\\t"); + assertDequote("\u000B", "\\v"); + assertDequote("\\", "\\\\"); + assertDequote("\"", "\\\""); + } + + public void testDequote_OctalAll() { + for (int i = 0; i < 127; i++) { + assertDequote("" + (char) i, octalEscape(i)); + } + for (int i = 128; i < 256; i++) { + int f = 0xC0 | (i >> 6); + int s = 0x80 | (i & 0x3f); + assertDequote("" + (char) i, octalEscape(f)+octalEscape(s)); + } + } + + private String octalEscape(int i) { + String s = Integer.toOctalString(i); + while (s.length() < 3) { + s = "0" + s; + } + return "\\"+s; + } + + public void testQuote_OctalAll() { + assertQuote("\\001", "\1"); + assertQuote("\\176", "~"); + assertQuote("\\303\\277", "\u00ff"); // \u00ff in UTF-8 + } + + public void testDequote_UnknownEscapeQ() { + assertDequote("\\q", "\\q"); + } + + public void testDequote_FooTabBar() { + assertDequote("foo\tbar", "foo\\tbar"); + } + + public void testDequote_Latin1() { + assertDequote("\u00c5ngstr\u00f6m", "\\305ngstr\\366m"); // Latin1 + } + + public void testDequote_UTF8() { + assertDequote("\u00c5ngstr\u00f6m", "\\303\\205ngstr\\303\\266m"); + } + + public void testDequote_RawUTF8() { + assertDequote("\u00c5ngstr\u00f6m", "\303\205ngstr\303\266m"); + } + + public void testDequote_RawLatin1() { + assertDequote("\u00c5ngstr\u00f6m", "\305ngstr\366m"); + } + + public void testQuote_Ang() { + assertQuote("\\303\\205ngstr\\303\\266m", "\u00c5ngstr\u00f6m"); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_HexParseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_HexParseTest.java new file mode 100644 index 000000000..a2c9e9dbd --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_HexParseTest.java @@ -0,0 +1,164 @@ +/* + * 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; + +import junit.framework.TestCase; + +import org.eclipse.jgit.lib.Constants; + +public class RawParseUtils_HexParseTest extends TestCase { + public void testInt4_1() { + assertEquals(0, RawParseUtils.parseHexInt4((byte) '0')); + assertEquals(1, RawParseUtils.parseHexInt4((byte) '1')); + assertEquals(2, RawParseUtils.parseHexInt4((byte) '2')); + assertEquals(3, RawParseUtils.parseHexInt4((byte) '3')); + assertEquals(4, RawParseUtils.parseHexInt4((byte) '4')); + assertEquals(5, RawParseUtils.parseHexInt4((byte) '5')); + assertEquals(6, RawParseUtils.parseHexInt4((byte) '6')); + assertEquals(7, RawParseUtils.parseHexInt4((byte) '7')); + assertEquals(8, RawParseUtils.parseHexInt4((byte) '8')); + assertEquals(9, RawParseUtils.parseHexInt4((byte) '9')); + assertEquals(10, RawParseUtils.parseHexInt4((byte) 'a')); + assertEquals(11, RawParseUtils.parseHexInt4((byte) 'b')); + assertEquals(12, RawParseUtils.parseHexInt4((byte) 'c')); + assertEquals(13, RawParseUtils.parseHexInt4((byte) 'd')); + assertEquals(14, RawParseUtils.parseHexInt4((byte) 'e')); + assertEquals(15, RawParseUtils.parseHexInt4((byte) 'f')); + + assertEquals(10, RawParseUtils.parseHexInt4((byte) 'A')); + assertEquals(11, RawParseUtils.parseHexInt4((byte) 'B')); + assertEquals(12, RawParseUtils.parseHexInt4((byte) 'C')); + assertEquals(13, RawParseUtils.parseHexInt4((byte) 'D')); + assertEquals(14, RawParseUtils.parseHexInt4((byte) 'E')); + assertEquals(15, RawParseUtils.parseHexInt4((byte) 'F')); + + assertNotHex('q'); + assertNotHex(' '); + assertNotHex('.'); + } + + private static void assertNotHex(final char c) { + try { + RawParseUtils.parseHexInt4((byte) c); + fail("Incorrectly acccepted " + c); + } catch (ArrayIndexOutOfBoundsException e) { + // pass + } + } + + public void testInt16() { + assertEquals(0x0000, parse16("0000")); + assertEquals(0x0001, parse16("0001")); + assertEquals(0x1234, parse16("1234")); + assertEquals(0xdead, parse16("dead")); + assertEquals(0xBEEF, parse16("BEEF")); + assertEquals(0x4321, parse16("4321")); + assertEquals(0xffff, parse16("ffff")); + + try { + parse16("noth"); + fail("Incorrectly acccepted \"noth\""); + } catch (ArrayIndexOutOfBoundsException e) { + // pass + } + + try { + parse16("01"); + fail("Incorrectly acccepted \"01\""); + } catch (ArrayIndexOutOfBoundsException e) { + // pass + } + + try { + parse16("000."); + fail("Incorrectly acccepted \"000.\""); + } catch (ArrayIndexOutOfBoundsException e) { + // pass + } + } + + private static int parse16(final String str) { + return RawParseUtils.parseHexInt16(Constants.encodeASCII(str), 0); + } + + public void testInt32() { + assertEquals(0x00000000, parse32("00000000")); + assertEquals(0x00000001, parse32("00000001")); + assertEquals(0xc0ffEE42, parse32("c0ffEE42")); + assertEquals(0xffffffff, parse32("ffffffff")); + assertEquals(-1, parse32("ffffffff")); + + try { + parse32("noth"); + fail("Incorrectly acccepted \"noth\""); + } catch (ArrayIndexOutOfBoundsException e) { + // pass + } + + try { + parse32("notahexs"); + fail("Incorrectly acccepted \"notahexs\""); + } catch (ArrayIndexOutOfBoundsException e) { + // pass + } + + try { + parse32("01"); + fail("Incorrectly acccepted \"01\""); + } catch (ArrayIndexOutOfBoundsException e) { + // pass + } + + try { + parse32("0000000."); + fail("Incorrectly acccepted \"0000000.\""); + } catch (ArrayIndexOutOfBoundsException e) { + // pass + } + } + + private static int parse32(final String str) { + return RawParseUtils.parseHexInt32(Constants.encodeASCII(str), 0); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java new file mode 100644 index 000000000..6dcd56ee0 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_LineMapTest.java @@ -0,0 +1,98 @@ +/* + * 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.util; + +import java.io.UnsupportedEncodingException; + +import junit.framework.TestCase; + +public class RawParseUtils_LineMapTest extends TestCase { + public void testEmpty() { + final IntList map = RawParseUtils.lineMap(new byte[] {}, 0, 0); + assertNotNull(map); + assertEquals(2, map.size()); + assertEquals(Integer.MIN_VALUE, map.get(0)); + assertEquals(0, map.get(1)); + } + + public void testOneBlankLine() { + final IntList map = RawParseUtils.lineMap(new byte[] { '\n' }, 0, 1); + assertEquals(3, map.size()); + assertEquals(Integer.MIN_VALUE, map.get(0)); + assertEquals(0, map.get(1)); + assertEquals(1, map.get(2)); + } + + public void testTwoLineFooBar() throws UnsupportedEncodingException { + final byte[] buf = "foo\nbar\n".getBytes("ISO-8859-1"); + final IntList map = RawParseUtils.lineMap(buf, 0, buf.length); + assertEquals(4, map.size()); + assertEquals(Integer.MIN_VALUE, map.get(0)); + assertEquals(0, map.get(1)); + assertEquals(4, map.get(2)); + assertEquals(buf.length, map.get(3)); + } + + public void testTwoLineNoLF() throws UnsupportedEncodingException { + final byte[] buf = "foo\nbar".getBytes("ISO-8859-1"); + final IntList map = RawParseUtils.lineMap(buf, 0, buf.length); + assertEquals(4, map.size()); + assertEquals(Integer.MIN_VALUE, map.get(0)); + assertEquals(0, map.get(1)); + assertEquals(4, map.get(2)); + assertEquals(buf.length, map.get(3)); + } + + public void testFourLineBlanks() throws UnsupportedEncodingException { + final byte[] buf = "foo\n\n\nbar\n".getBytes("ISO-8859-1"); + final IntList map = RawParseUtils.lineMap(buf, 0, buf.length); + assertEquals(6, map.size()); + assertEquals(Integer.MIN_VALUE, map.get(0)); + assertEquals(0, map.get(1)); + assertEquals(4, map.get(2)); + assertEquals(5, map.get(3)); + assertEquals(6, map.get(4)); + assertEquals(buf.length, map.get(5)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_MatchTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_MatchTest.java new file mode 100644 index 000000000..29459326d --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RawParseUtils_MatchTest.java @@ -0,0 +1,75 @@ +/* + * 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; + +import junit.framework.TestCase; + +import org.eclipse.jgit.lib.Constants; + +public class RawParseUtils_MatchTest extends TestCase { + public void testMatch_Equal() { + final byte[] src = Constants.encodeASCII(" differ\n"); + final byte[] dst = Constants.encodeASCII("foo differ\n"); + assertTrue(RawParseUtils.match(dst, 3, src) == 3 + src.length); + } + + public void testMatch_NotEqual() { + final byte[] src = Constants.encodeASCII(" differ\n"); + final byte[] dst = Constants.encodeASCII("a differ\n"); + assertTrue(RawParseUtils.match(dst, 2, src) < 0); + } + + public void testMatch_Prefix() { + final byte[] src = Constants.encodeASCII("author "); + final byte[] dst = Constants.encodeASCII("author A. U. Thor"); + assertTrue(RawParseUtils.match(dst, 0, src) == src.length); + assertTrue(RawParseUtils.match(dst, 1, src) < 0); + } + + public void testMatch_TooSmall() { + final byte[] src = Constants.encodeASCII("author "); + final byte[] dst = Constants.encodeASCII("author autho"); + assertTrue(RawParseUtils.match(dst, src.length + 1, src) < 0); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java new file mode 100644 index 000000000..912380dcd --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/StringUtilsTest.java @@ -0,0 +1,84 @@ +/* + * 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; + +import junit.framework.TestCase; + +public class StringUtilsTest extends TestCase { + public void testToLowerCaseChar() { + assertEquals('a', StringUtils.toLowerCase('A')); + assertEquals('z', StringUtils.toLowerCase('Z')); + + assertEquals('a', StringUtils.toLowerCase('a')); + assertEquals('z', StringUtils.toLowerCase('z')); + + assertEquals((char) 0, StringUtils.toLowerCase((char) 0)); + assertEquals((char) 0xffff, StringUtils.toLowerCase((char) 0xffff)); + } + + public void testToLowerCaseString() { + assertEquals("\n abcdefghijklmnopqrstuvwxyz\n", StringUtils + .toLowerCase("\n ABCDEFGHIJKLMNOPQRSTUVWXYZ\n")); + } + + public void testEqualsIgnoreCase1() { + final String a = "FOO"; + assertTrue(StringUtils.equalsIgnoreCase(a, a)); + } + + public void testEqualsIgnoreCase2() { + assertFalse(StringUtils.equalsIgnoreCase("a", "")); + } + + public void testEqualsIgnoreCase3() { + assertFalse(StringUtils.equalsIgnoreCase("a", "b")); + assertFalse(StringUtils.equalsIgnoreCase("ac", "ab")); + } + + public void testEqualsIgnoreCase4() { + assertTrue(StringUtils.equalsIgnoreCase("a", "a")); + assertTrue(StringUtils.equalsIgnoreCase("A", "a")); + assertTrue(StringUtils.equalsIgnoreCase("a", "A")); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/TemporaryBufferTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/TemporaryBufferTest.java new file mode 100644 index 000000000..eb2417224 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/TemporaryBufferTest.java @@ -0,0 +1,380 @@ +/* + * 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.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +import junit.framework.TestCase; + +public class TemporaryBufferTest extends TestCase { + public void testEmpty() throws IOException { + final TemporaryBuffer b = new TemporaryBuffer(); + try { + b.close(); + assertEquals(0, b.length()); + final byte[] r = b.toByteArray(); + assertNotNull(r); + assertEquals(0, r.length); + } finally { + b.destroy(); + } + } + + public void testOneByte() throws IOException { + final TemporaryBuffer b = new TemporaryBuffer(); + final byte test = (byte) new TestRng(getName()).nextInt(); + try { + b.write(test); + b.close(); + assertEquals(1, b.length()); + { + final byte[] r = b.toByteArray(); + assertNotNull(r); + assertEquals(1, r.length); + assertEquals(test, r[0]); + } + { + final ByteArrayOutputStream o = new ByteArrayOutputStream(); + b.writeTo(o, null); + o.close(); + final byte[] r = o.toByteArray(); + assertEquals(1, r.length); + assertEquals(test, r[0]); + } + } finally { + b.destroy(); + } + } + + public void testOneBlock_BulkWrite() throws IOException { + final TemporaryBuffer b = new TemporaryBuffer(); + final byte[] test = new TestRng(getName()) + .nextBytes(TemporaryBuffer.Block.SZ); + try { + b.write(test, 0, 2); + b.write(test, 2, 4); + b.write(test, 6, test.length - 6 - 2); + b.write(test, test.length - 2, 2); + b.close(); + assertEquals(test.length, b.length()); + { + final byte[] r = b.toByteArray(); + assertNotNull(r); + assertEquals(test.length, r.length); + assertTrue(Arrays.equals(test, r)); + } + { + final ByteArrayOutputStream o = new ByteArrayOutputStream(); + b.writeTo(o, null); + o.close(); + final byte[] r = o.toByteArray(); + assertEquals(test.length, r.length); + assertTrue(Arrays.equals(test, r)); + } + } finally { + b.destroy(); + } + } + + public void testOneBlockAndHalf_BulkWrite() throws IOException { + final TemporaryBuffer b = new TemporaryBuffer(); + final byte[] test = new TestRng(getName()) + .nextBytes(TemporaryBuffer.Block.SZ * 3 / 2); + try { + b.write(test, 0, 2); + b.write(test, 2, 4); + b.write(test, 6, test.length - 6 - 2); + b.write(test, test.length - 2, 2); + b.close(); + assertEquals(test.length, b.length()); + { + final byte[] r = b.toByteArray(); + assertNotNull(r); + assertEquals(test.length, r.length); + assertTrue(Arrays.equals(test, r)); + } + { + final ByteArrayOutputStream o = new ByteArrayOutputStream(); + b.writeTo(o, null); + o.close(); + final byte[] r = o.toByteArray(); + assertEquals(test.length, r.length); + assertTrue(Arrays.equals(test, r)); + } + } finally { + b.destroy(); + } + } + + public void testOneBlockAndHalf_SingleWrite() throws IOException { + final TemporaryBuffer b = new TemporaryBuffer(); + final byte[] test = new TestRng(getName()) + .nextBytes(TemporaryBuffer.Block.SZ * 3 / 2); + try { + for (int i = 0; i < test.length; i++) + b.write(test[i]); + b.close(); + assertEquals(test.length, b.length()); + { + final byte[] r = b.toByteArray(); + assertNotNull(r); + assertEquals(test.length, r.length); + assertTrue(Arrays.equals(test, r)); + } + { + final ByteArrayOutputStream o = new ByteArrayOutputStream(); + b.writeTo(o, null); + o.close(); + final byte[] r = o.toByteArray(); + assertEquals(test.length, r.length); + assertTrue(Arrays.equals(test, r)); + } + } finally { + b.destroy(); + } + } + + public void testOneBlockAndHalf_Copy() throws IOException { + final TemporaryBuffer b = new TemporaryBuffer(); + final byte[] test = new TestRng(getName()) + .nextBytes(TemporaryBuffer.Block.SZ * 3 / 2); + try { + final ByteArrayInputStream in = new ByteArrayInputStream(test); + b.write(in.read()); + b.copy(in); + b.close(); + assertEquals(test.length, b.length()); + { + final byte[] r = b.toByteArray(); + assertNotNull(r); + assertEquals(test.length, r.length); + assertTrue(Arrays.equals(test, r)); + } + { + final ByteArrayOutputStream o = new ByteArrayOutputStream(); + b.writeTo(o, null); + o.close(); + final byte[] r = o.toByteArray(); + assertEquals(test.length, r.length); + assertTrue(Arrays.equals(test, r)); + } + } finally { + b.destroy(); + } + } + + public void testLarge_SingleWrite() throws IOException { + final TemporaryBuffer b = new TemporaryBuffer(); + final byte[] test = new TestRng(getName()) + .nextBytes(TemporaryBuffer.DEFAULT_IN_CORE_LIMIT * 3); + try { + b.write(test); + b.close(); + assertEquals(test.length, b.length()); + { + final byte[] r = b.toByteArray(); + assertNotNull(r); + assertEquals(test.length, r.length); + assertTrue(Arrays.equals(test, r)); + } + { + final ByteArrayOutputStream o = new ByteArrayOutputStream(); + b.writeTo(o, null); + o.close(); + final byte[] r = o.toByteArray(); + assertEquals(test.length, r.length); + assertTrue(Arrays.equals(test, r)); + } + } finally { + b.destroy(); + } + } + + public void testInCoreLimit_SwitchOnAppendByte() throws IOException { + final TemporaryBuffer b = new TemporaryBuffer(); + final byte[] test = new TestRng(getName()) + .nextBytes(TemporaryBuffer.DEFAULT_IN_CORE_LIMIT + 1); + try { + b.write(test, 0, test.length - 1); + b.write(test[test.length - 1]); + b.close(); + assertEquals(test.length, b.length()); + { + final byte[] r = b.toByteArray(); + assertNotNull(r); + assertEquals(test.length, r.length); + assertTrue(Arrays.equals(test, r)); + } + { + final ByteArrayOutputStream o = new ByteArrayOutputStream(); + b.writeTo(o, null); + o.close(); + final byte[] r = o.toByteArray(); + assertEquals(test.length, r.length); + assertTrue(Arrays.equals(test, r)); + } + } finally { + b.destroy(); + } + } + + public void testInCoreLimit_SwitchBeforeAppendByte() throws IOException { + final TemporaryBuffer b = new TemporaryBuffer(); + final byte[] test = new TestRng(getName()) + .nextBytes(TemporaryBuffer.DEFAULT_IN_CORE_LIMIT * 3); + try { + b.write(test, 0, test.length - 1); + b.write(test[test.length - 1]); + b.close(); + assertEquals(test.length, b.length()); + { + final byte[] r = b.toByteArray(); + assertNotNull(r); + assertEquals(test.length, r.length); + assertTrue(Arrays.equals(test, r)); + } + { + final ByteArrayOutputStream o = new ByteArrayOutputStream(); + b.writeTo(o, null); + o.close(); + final byte[] r = o.toByteArray(); + assertEquals(test.length, r.length); + assertTrue(Arrays.equals(test, r)); + } + } finally { + b.destroy(); + } + } + + public void testInCoreLimit_SwitchOnCopy() throws IOException { + final TemporaryBuffer b = new TemporaryBuffer(); + final byte[] test = new TestRng(getName()) + .nextBytes(TemporaryBuffer.DEFAULT_IN_CORE_LIMIT * 2); + try { + final ByteArrayInputStream in = new ByteArrayInputStream(test, + TemporaryBuffer.DEFAULT_IN_CORE_LIMIT, test.length + - TemporaryBuffer.DEFAULT_IN_CORE_LIMIT); + b.write(test, 0, TemporaryBuffer.DEFAULT_IN_CORE_LIMIT); + b.copy(in); + b.close(); + assertEquals(test.length, b.length()); + { + final byte[] r = b.toByteArray(); + assertNotNull(r); + assertEquals(test.length, r.length); + assertTrue(Arrays.equals(test, r)); + } + { + final ByteArrayOutputStream o = new ByteArrayOutputStream(); + b.writeTo(o, null); + o.close(); + final byte[] r = o.toByteArray(); + assertEquals(test.length, r.length); + assertTrue(Arrays.equals(test, r)); + } + } finally { + b.destroy(); + } + } + + public void testDestroyWhileOpen() throws IOException { + final TemporaryBuffer b = new TemporaryBuffer(); + try { + b.write(new TestRng(getName()) + .nextBytes(TemporaryBuffer.DEFAULT_IN_CORE_LIMIT * 2)); + } finally { + b.destroy(); + } + } + + public void testRandomWrites() throws IOException { + final TemporaryBuffer b = new TemporaryBuffer(); + final TestRng rng = new TestRng(getName()); + final int max = TemporaryBuffer.DEFAULT_IN_CORE_LIMIT * 2; + final byte[] expect = new byte[max]; + try { + int written = 0; + boolean onebyte = true; + while (written < max) { + if (onebyte) { + final byte v = (byte) rng.nextInt(); + b.write(v); + expect[written++] = v; + } else { + final int len = Math + .min(rng.nextInt() & 127, max - written); + final byte[] tmp = rng.nextBytes(len); + b.write(tmp, 0, len); + System.arraycopy(tmp, 0, expect, written, len); + written += len; + } + onebyte = !onebyte; + } + assertEquals(expect.length, written); + b.close(); + + assertEquals(expect.length, b.length()); + { + final byte[] r = b.toByteArray(); + assertNotNull(r); + assertEquals(expect.length, r.length); + assertTrue(Arrays.equals(expect, r)); + } + { + final ByteArrayOutputStream o = new ByteArrayOutputStream(); + b.writeTo(o, null); + o.close(); + final byte[] r = o.toByteArray(); + assertEquals(expect.length, r.length); + assertTrue(Arrays.equals(expect, r)); + } + } finally { + b.destroy(); + } + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/TestRng.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/TestRng.java new file mode 100644 index 000000000..4110b55ba --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/TestRng.java @@ -0,0 +1,67 @@ +/* + * 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; + +/** Toy RNG to ensure we get predictable numbers during unit tests. */ +public class TestRng { + private int next; + + public TestRng(final String seed) { + next = 0; + for (int i = 0; i < seed.length(); i++) + next = next * 11 + seed.charAt(i); + } + + public byte[] nextBytes(final int cnt) { + final byte[] r = new byte[cnt]; + for (int i = 0; i < cnt; i++) + r[i] = (byte) nextInt(); + return r; + } + + public int nextInt() { + next = next * 1103515245 + 12345; + return next; + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutInputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutInputStreamTest.java new file mode 100644 index 000000000..cd16288b6 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutInputStreamTest.java @@ -0,0 +1,189 @@ +/* + * 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.PipedInputStream; +import java.io.PipedOutputStream; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.io.InterruptTimer; +import org.eclipse.jgit.util.io.TimeoutInputStream; + +import junit.framework.TestCase; + +public class TimeoutInputStreamTest extends TestCase { + private static final int timeout = 250; + + private PipedOutputStream out; + + private PipedInputStream in; + + private InterruptTimer timer; + + private TimeoutInputStream is; + + private long start; + + protected void setUp() throws Exception { + super.setUp(); + out = new PipedOutputStream(); + in = new PipedInputStream(out); + timer = new InterruptTimer(); + is = new TimeoutInputStream(in, timer); + is.setTimeout(timeout); + } + + protected void tearDown() throws Exception { + timer.terminate(); + for (Thread t : active()) + assertFalse(t instanceof InterruptTimer.AlarmThread); + super.tearDown(); + } + + public void testTimeout_readByte_Success1() throws IOException { + out.write('a'); + assertEquals('a', is.read()); + } + + public void testTimeout_readByte_Success2() throws IOException { + final byte[] exp = new byte[] { 'a', 'b', 'c' }; + out.write(exp); + assertEquals(exp[0], is.read()); + assertEquals(exp[1], is.read()); + assertEquals(exp[2], is.read()); + out.close(); + assertEquals(-1, is.read()); + } + + public void testTimeout_readByte_Timeout() throws IOException { + beginRead(); + try { + is.read(); + fail("incorrectly read a byte"); + } catch (InterruptedIOException e) { + // expected + } + assertTimeout(); + } + + public void testTimeout_readBuffer_Success1() throws IOException { + final byte[] exp = new byte[] { 'a', 'b', 'c' }; + final byte[] act = new byte[exp.length]; + out.write(exp); + NB.readFully(is, act, 0, act.length); + assertTrue(Arrays.equals(exp, act)); + } + + public void testTimeout_readBuffer_Success2() throws IOException { + final byte[] exp = new byte[] { 'a', 'b', 'c' }; + final byte[] act = new byte[exp.length]; + out.write(exp); + NB.readFully(is, act, 0, 1); + NB.readFully(is, act, 1, 1); + NB.readFully(is, act, 2, 1); + assertTrue(Arrays.equals(exp, act)); + } + + public void testTimeout_readBuffer_Timeout() throws IOException { + beginRead(); + try { + NB.readFully(is, new byte[512], 0, 512); + fail("incorrectly read bytes"); + } catch (InterruptedIOException e) { + // expected + } + assertTimeout(); + } + + public void testTimeout_skip_Success() throws IOException { + final byte[] exp = new byte[] { 'a', 'b', 'c' }; + out.write(exp); + assertEquals(2, is.skip(2)); + assertEquals('c', is.read()); + } + + public void testTimeout_skip_Timeout() throws IOException { + beginRead(); + try { + is.skip(1024); + fail("incorrectly skipped bytes"); + } catch (InterruptedIOException e) { + // expected + } + assertTimeout(); + } + + private void beginRead() { + start = now(); + } + + private void assertTimeout() { + // Our timeout was supposed to be ~250 ms. Since this is a timing + // test we can't assume we spent *exactly* the timeout period, as + // there may be other activity going on in the system. Instead we + // look for the delta between the start and end times to be within + // 50 ms of the expected timeout. + // + final long wait = now() - start; + assertTrue(Math.abs(wait - timeout) < 50); + } + + private static List active() { + Thread[] all = new Thread[16]; + int n = Thread.currentThread().getThreadGroup().enumerate(all); + while (n == all.length) { + all = new Thread[all.length * 2]; + n = Thread.currentThread().getThreadGroup().enumerate(all); + } + return Arrays.asList(all).subList(0, n); + } + + private static long now() { + return System.currentTimeMillis(); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutOutputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutOutputStreamTest.java new file mode 100644 index 000000000..bba8640ff --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TimeoutOutputStreamTest.java @@ -0,0 +1,285 @@ +/* + * 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; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.io.InterruptTimer; +import org.eclipse.jgit.util.io.TimeoutOutputStream; + +import junit.framework.TestCase; + +public class TimeoutOutputStreamTest extends TestCase { + private static final int timeout = 250; + + private PipedOutputStream out; + + private FullPipeInputStream in; + + private InterruptTimer timer; + + private TimeoutOutputStream os; + + private long start; + + protected void setUp() throws Exception { + super.setUp(); + out = new PipedOutputStream(); + in = new FullPipeInputStream(out); + timer = new InterruptTimer(); + os = new TimeoutOutputStream(out, timer); + os.setTimeout(timeout); + } + + protected void tearDown() throws Exception { + timer.terminate(); + for (Thread t : active()) + assertFalse(t instanceof InterruptTimer.AlarmThread); + super.tearDown(); + } + + public void testTimeout_writeByte_Success1() throws IOException { + in.free(1); + os.write('a'); + in.want(1); + assertEquals('a', in.read()); + } + + public void testTimeout_writeByte_Success2() throws IOException { + final byte[] exp = new byte[] { 'a', 'b', 'c' }; + final byte[] act = new byte[exp.length]; + in.free(exp.length); + os.write(exp[0]); + os.write(exp[1]); + os.write(exp[2]); + in.want(exp.length); + in.read(act); + assertTrue(Arrays.equals(exp, act)); + } + + public void testTimeout_writeByte_Timeout() throws IOException { + beginWrite(); + try { + os.write('\n'); + fail("incorrectly write a byte"); + } catch (InterruptedIOException e) { + // expected + } + assertTimeout(); + } + + public void testTimeout_writeBuffer_Success1() throws IOException { + final byte[] exp = new byte[] { 'a', 'b', 'c' }; + final byte[] act = new byte[exp.length]; + in.free(exp.length); + os.write(exp); + in.want(exp.length); + in.read(act); + assertTrue(Arrays.equals(exp, act)); + } + + public void testTimeout_writeBuffer_Timeout() throws IOException { + beginWrite(); + try { + os.write(new byte[512]); + fail("incorrectly wrote bytes"); + } catch (InterruptedIOException e) { + // expected + } + assertTimeout(); + } + + public void testTimeout_flush_Success() throws IOException { + final boolean[] called = new boolean[1]; + os = new TimeoutOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + fail("should not have written"); + } + + @Override + public void flush() throws IOException { + called[0] = true; + } + }, timer); + os.setTimeout(timeout); + os.flush(); + assertTrue(called[0]); + } + + public void testTimeout_flush_Timeout() throws IOException { + final boolean[] called = new boolean[1]; + os = new TimeoutOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + fail("should not have written"); + } + + @Override + public void flush() throws IOException { + called[0] = true; + for (;;) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new InterruptedIOException(); + } + } + } + }, timer); + os.setTimeout(timeout); + + beginWrite(); + try { + os.flush(); + fail("incorrectly flushed"); + } catch (InterruptedIOException e) { + // expected + } + assertTimeout(); + assertTrue(called[0]); + } + + public void testTimeout_close_Success() throws IOException { + final boolean[] called = new boolean[1]; + os = new TimeoutOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + fail("should not have written"); + } + + @Override + public void close() throws IOException { + called[0] = true; + } + }, timer); + os.setTimeout(timeout); + os.close(); + assertTrue(called[0]); + } + + public void testTimeout_close_Timeout() throws IOException { + final boolean[] called = new boolean[1]; + os = new TimeoutOutputStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + fail("should not have written"); + } + + @Override + public void close() throws IOException { + called[0] = true; + for (;;) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new InterruptedIOException(); + } + } + } + }, timer); + os.setTimeout(timeout); + + beginWrite(); + try { + os.close(); + fail("incorrectly closed"); + } catch (InterruptedIOException e) { + // expected + } + assertTimeout(); + assertTrue(called[0]); + } + + private void beginWrite() { + start = now(); + } + + private void assertTimeout() { + // Our timeout was supposed to be ~250 ms. Since this is a timing + // test we can't assume we spent *exactly* the timeout period, as + // there may be other activity going on in the system. Instead we + // look for the delta between the start and end times to be within + // 50 ms of the expected timeout. + // + final long wait = now() - start; + assertTrue(Math.abs(wait - timeout) < 50); + } + + private static List active() { + Thread[] all = new Thread[16]; + int n = Thread.currentThread().getThreadGroup().enumerate(all); + while (n == all.length) { + all = new Thread[all.length * 2]; + n = Thread.currentThread().getThreadGroup().enumerate(all); + } + return Arrays.asList(all).subList(0, n); + } + + private static long now() { + return System.currentTimeMillis(); + } + + private final class FullPipeInputStream extends PipedInputStream { + FullPipeInputStream(PipedOutputStream src) throws IOException { + super(src); + src.write(new byte[PIPE_SIZE]); + } + + void want(int cnt) throws IOException { + NB.skipFully(this, PIPE_SIZE - cnt); + } + + void free(int cnt) throws IOException { + NB.skipFully(this, cnt); + } + } +} diff --git a/org.eclipse.jgit/.classpath b/org.eclipse.jgit/.classpath new file mode 100644 index 000000000..304e86186 --- /dev/null +++ b/org.eclipse.jgit/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.eclipse.jgit/.fbprefs b/org.eclipse.jgit/.fbprefs new file mode 100644 index 000000000..81a0767ff --- /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 000000000..ba077a403 --- /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 000000000..19aeef1fb --- /dev/null +++ b/org.eclipse.jgit/.project @@ -0,0 +1,28 @@ + + + org.eclipse.jgit + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + + 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 000000000..66ac15c47 --- /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/=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 000000000..cce05685b --- /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 000000000..8e8e17240 --- /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 000000000..709a44074 --- /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= diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF new file mode 100644 index 000000000..fed005ccd --- /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 000000000..aa1a00826 --- /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 000000000..ba9d1d099 --- /dev/null +++ b/org.eclipse.jgit/findBugs/FindBugsExcludeFilter.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit/plugin.properties b/org.eclipse.jgit/plugin.properties new file mode 100644 index 000000000..b075cc6f2 --- /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 000000000..647b103d2 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 { + + final GraphCellRender cell; + + Graphics2D g; + + AWTPlotRenderer(final GraphCellRender c) { + cell = c; + } + + void paint(final Graphics in, final PlotCommit 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 000000000..ccd47ddda --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/awtui/AwtAuthenticator.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 cached = new ArrayList(); + + @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 000000000..8d78e47df --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/awtui/CommitGraphPane.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

+ * This class is currently a very primitive commit visualization tool. It shows + * a table of 3 columns: + *

    + *
  1. Commit graph and short message
  2. + *
  3. Author name and email address
  4. + *
  5. Author date and time
  6. + * + */ +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 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 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 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 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) 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 000000000..4a1196447 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/awtui/SwingCommitList.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 { + final LinkedList colors; + + SwingCommitList() { + colors = new LinkedList(); + 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 000000000..639ed77ee --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 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 e, final int i) { + return e.get(i).getBeginA() - e.get(i - 1).getEndA() <= 2 * context; + } + + private boolean combineB(final List 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 000000000..109c049cc --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2008-2009, Johannes E. Schindelin + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * An edit covers the modified region only. It does not cover a common region. + *

    + * Regions should be specified using 0 based notation, so add 1 to the start and + * end marks for line numbers in a file. + *

    + * An edit where beginA == endA && beginB < endB is an insert edit, + * that is sequence B inserted the elements in region + * [beginB, endB) at beginA. + *

    + * An edit where beginA < endA && beginB == endB is a delete edit, + * that is sequence B has removed the elements between + * [beginA, endA). + *

    + * An edit where beginA < endA && beginB < endB is a replace edit, + * that is sequence B has replaced the range of elements between + * [beginA, endA) with those found in [beginB, endB). + */ +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 000000000..85a539644 --- /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 { + private final ArrayList container; + + /** Create a new, empty edit list. */ + public EditList() { + container = new ArrayList(); + } + + @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 000000000..61a3ef41c --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * The specified line is copied as-is, with no character encoding + * translation performed. + *

    + * If the specified line ends with an LF ('\n'), the LF is not + * 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 [ptr, end) 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 000000000..3a3356411 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Sequence.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2008-2009, Johannes E. Schindelin + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * A sequence of elements is defined to contain elements in the index range + * [0, {@link #size()}), 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. + *

    + * 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. + *

    + * 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. + *

    + * Implementations must ensure equals(thisIdx,other,otherIdx) + * returns the same as other.equals(otherIdx,this,thisIdx). + * + * @param thisIdx + * index within this sequence; must be in the range + * [ 0, this.size() ). + * @param other + * another sequence; must be the same implementation class, that + * is this.getClass() == other.getClass(). + * @param otherIdx + * index within other sequence; must be in the range + * [ 0, other.size() ). + * @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 000000000..70f80aeb7 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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}. + *

    + * 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}. + *

    + * 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. + *

    + * 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. + *

    + * The entry is placed at the end of the entry list. The caller is + * responsible for making sure the final table is correctly sorted. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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}. + *

    + * 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. + *

    + * After completion the DirCache returned by {@link #getDirCache()} will + * contain all modifications. + *

    + * Note to implementors: 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}. + *

    + * 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. + *

    + * If this method fails (returns false) the lock is still released. + *

    + * 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 000000000..d6676e41f --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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). + *

    + * 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. + *

    + * 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 ENT_CMP = new Comparator() { + 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * Note that this value counts only files. + * + * @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 i. + */ + 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 i. + */ + 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. + *

    + * 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 build 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 000000000..ce1d0638b --- /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 + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 TreeWalk. + *

    + * 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. + *

    + * This iterator is suitable for update processes, or even a simple delete + * algorithm. For example deleting a path: + * + *

    + * 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("name/to/remove"));
    + * walk.addTree(new DirCacheBuildIterator(edit));
    + *
    + * while (walk.next())
    + * 	; // do nothing on a match as we want to remove matches
    + * edit.commit();
    + * 
    + */ +public class DirCacheBuildIterator extends DirCacheIterator { + private final DirCacheBuilder builder; + + /** + * Create a new iterator for an already loaded DirCache instance. + *

    + * 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 000000000..f294f5cf5 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * A builder always starts from a clean slate and appends in every single + * DirCacheEntry which the final updated index must have to reflect + * its new content. + *

    + * 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. + *

    + * 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()}. + *

    + * 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. + *

    + * 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()}. + *

    + * 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. + *

    + * 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. + *

    + * If pathPrefix is "a/b" and the tree contains file "c" then the resulting + * DirCacheEntry will have the path "a/b/c". + *

    + * 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 pathPrefix. 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 000000000..1ad8e355d --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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}. + *

    + * + * @see DirCacheBuilder + */ +public class DirCacheEditor extends BaseDirCacheEditor { + private static final Comparator EDIT_CMP = new Comparator() { + 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 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(); + } + + /** + * Append one edit command to the list of commands to be applied. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 000000000..d3e118a55 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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}. + *

    + * 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? + *

    + * 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. + *

    + * 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? + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 000000000..9c4718782 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 TreeWalk. + *

    + * This is an iterator to adapt a loaded DirCache instance (such as + * read from an existing .git/index file) to the tree structure + * used by a TreeWalk, 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. + *

    + * 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 000000000..fc29aa71b --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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 TREE_CMP = new Comparator() { + 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 cache that is a member of this + * tree. The path of cache[cacheIdx].path for the + * range [0,pathOff-1) matches the complete path of + * this tree, from the root of the repository. + * @param pathOffset + * number of bytes of cache[cacheIdx].path that + * matches this tree's path. The value at array position + * cache[cacheIdx].path[pathOff-1] is always '/' if + * pathOff 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 cache that are valid for + * iteration. + * @param cIdx + * first position of cache that is a member of this + * tree. The path of cache[cacheIdx].path for the + * range [0,pathOff-1) matches the complete path of + * this tree, from the root of the repository. + * @param pathOff + * number of bytes of cache[cacheIdx].path that + * matches this tree's path. The value at array position + * cache[cacheIdx].path[pathOff-1] is always '/' if + * pathOff 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 000000000..8cfc366ea --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CheckoutConflictException.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..0fb14655f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CompoundException.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 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 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 why) { + super(format(why)); + causeList = Collections.unmodifiableList(new ArrayList(why)); + } + + /** + * Get the complete list of reasons why this failure happened. + * + * @return unmodifiable collection of all possible reasons. + */ + public List 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 000000000..43fb4bcf8 --- /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 000000000..f42b0d7e1 --- /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 + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..893ee9ceb --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/EntryExistsException.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..d501bda38 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/GitlinksNotSupportedException.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2009, Jonas Fonseca + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..7cf1de214 --- /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 + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..e6577213e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidObjectIdException.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2009, Jonas Fonseca + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..18e78ffe7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/InvalidPatternException.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2008, Florian Koeberle + * Copyright (C) 2008, Florian Köberle + * Copyright (C) 2009, Vasyl' Vavrychuk + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..92d63d299 --- /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 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 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 000000000..41cacb84a --- /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 + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..623dfa6ec --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoClosingBracketException.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2008, Florian Koeberle + * Copyright (C) 2008, Florian Köberle + * Copyright (C) 2009, Vasyl' Vavrychuk + * Copyright (C) 2009, Yann Simon + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..fe073d564 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoRemoteRepositoryException.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..87044cbf3 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NotSupportedException.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..8af1991b1 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/ObjectWritingException.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..a34b80db8 --- /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 000000000..b82846530 --- /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 000000000..f9a1bae93 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackProtocolException.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..d947a2cdc --- /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 000000000..0ad41ed17 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevWalkException.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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}. + *

    + * 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 000000000..58ec1b023 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevisionSyntaxException.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2006, Shawn O. Pearce + * Copyright (C) 2009, Vasyl' Vavrychuk + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..c9b51de36 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..60d7aa098 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/SymlinksNotSupportedException.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..2856eb62c --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/TransportException.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..51e651ca5 --- /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 000000000..42182965a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2008, Florian Koeberle + * Copyright (C) 2008, Florian Köberle + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 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 newHeads) { + if (this.newHeads != null) + throw new IllegalStateException("Property is already non null"); + this.newHeads = newHeads; + } + + public List 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 000000000..699eca968 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2008, Florian Koeberle + * Copyright (C) 2008, Florian Köberle + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..582123bb5 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2008, Florian Koeberle + * Copyright (C) 2008, Florian Köberle + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * Supported are the wildcard characters * and ? and groups with: + *

      + *
    • characters e.g. [abc]
    • + *
    • ranges e.g. [a-z]
    • + *
    • the following character classes + *
        + *
      • [:alnum:]
      • + *
      • [:alpha:]
      • + *
      • [:blank:]
      • + *
      • [:cntrl:]
      • + *
      • [:digit:]
      • + *
      • [:graph:]
      • + *
      • [:lower:]
      • + *
      • [:print:]
      • + *
      • [:punct:]
      • + *
      • [:space:]
      • + *
      • [:upper:]
      • + *
      • [:word:]
      • + *
      • [:xdigit:]
      • + *
      + * e. g. [[:xdigit:]]
    • + *
    + *

    + */ +public class FileNameMatcher { + static final List EMPTY_HEAD_LIST = Collections.emptyList(); + + private static final Pattern characterClassStartPattern = Pattern + .compile("\\[[.:=]"); + + private List headsStartValue; + + private List 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 listForLocalUseage; + + /** + * + * @param headsStartValue + * must be a list which will never be modified. + */ + private FileNameMatcher(final List 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 headsStartValue, + final List heads) { + this.headsStartValue = headsStartValue; + this.heads = new ArrayList(heads.size()); + this.heads.addAll(heads); + this.listForLocalUseage = new ArrayList(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 other. + * + * @param other + * another {@link FileNameMatcher} instance. + */ + public FileNameMatcher(FileNameMatcher other) { + this(other.headsStartValue, other.heads); + } + + private static List createHeadsStartValues( + final String patternString, final Character invalidWildgetCharacter) + throws InvalidPatternException { + + final List allHeads = parseHeads(patternString, + invalidWildgetCharacter); + + List nextHeadsSuggestion = new ArrayList(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(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 parseHeads(final String pattern, + final Character invalidWildgetCharacter) + throws InvalidPatternException { + + int currentIndex = 0; + List heads = new ArrayList(); + 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 createSimpleHeads( + final String patternPart, final Character invalidWildgetCharacter) { + final List heads = new ArrayList( + 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 newHeads = listForLocalUseage; + newHeads.clear(); + List lastAddedHeads = null; + for (int i = 0; i < heads.size(); i++) { + final Head head = heads.get(i); + final List 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 copyOfHeads = new ArrayList(heads.size()); + copyOfHeads.addAll(heads); + return new FileNameMatcher(copyOfHeads); + } + + /** + * + * @return true, if the string currently being matched does match. + */ + public boolean isMatch() { + final ListIterator 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 000000000..79f64f859 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2008, Florian Koeberle + * Copyright (C) 2008, Florian Köberle + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 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(); + 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 000000000..3de18a735 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/Head.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2008, Florian Koeberle + * Copyright (C) 2008, Florian Köberle + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 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 000000000..78a61b9c5 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2008, Florian Koeberle + * Copyright (C) 2008, Florian Köberle + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 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 000000000..6d527d2b2 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2008, Florian Koeberle + * Copyright (C) 2008, Florian Köberle + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..b5173d97d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/WildCardHead.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2008, Florian Koeberle + * Copyright (C) 2008, Florian Köberle + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..13c54201c --- /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}. + *

    + * 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. + *

    + * 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 (end-offset 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 <= 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 <0 if this abbreviation names an object that is less than + * other; 0 if this abbreviation exactly matches the + * first {@link #length()} digits of other.name(); + * >0 if this abbreviation names an object that is after + * other. + */ + 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 000000000..6052aa336 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbstractIndexTreeVisitor.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2006, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..311839e43 --- /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}. + *

    + * 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 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 000000000..4ed440354 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java @@ -0,0 +1,492 @@ +/* + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * This method is a utility for abbreviate(repo, 8). + * + * @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. + *

    + * 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. + *

    + * Only returns this if this instance is an unsubclassed + * instance of {@link ObjectId}; otherwise a new instance is returned + * holding the same value. + *

    + * 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. + *

    + * See {@link #copy()} if this is a possibly subclassed (but + * immutable) identity and the application needs a lightweight identity + * only reference. + * + * @return an immutable copy. May be this 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 000000000..461e6d402 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BinaryDelta.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * This entire class is heavily cribbed from patch-delta.c in the + * GIT project. The original delta patching code was written by Nicolas Pitre + * (<nico@cam.org>). + *

    + */ +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 000000000..0a4222fc3 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * 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 000000000..804261031 --- /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 + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..1b29934d2 --- /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 + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..89bbe7548 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ByteWindow.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + */ +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 dstbuf 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 + * pos. + * @return number of bytes actually copied; this may be less than + * cnt if cnt 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 dstbuf 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 + * pos. + * @return number of bytes actually copied; this may be less than + * cnt if cnt 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 dstbuf 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 dstoff based on the number of bytes + * successfully copied into dstbuf by + * inf. 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 dstbuf 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 dstoff based on the number of bytes + * successfully copied into dstbuf by + * inf. 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 000000000..cdfab7cc3 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Commit.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2006-2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2009, Google, Inc. + * Copyright (C) 2009, JetBrains s.r.o. + * Copyright (C) 2007-2008, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * Copyright (C) 2008, Thad Hughes + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * This state is copy-on-write. It should always contain an immutable list + * of the configuration keys/values. + */ + private final AtomicReference state; + + private final Config baseConfig; + + /** + * Magic value indicating a missing entry. + *

    + * 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(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 + *

    + * 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 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 getSubsections(final String section) { + return get(new SubsectionNames(section)); + } + + /** + * Obtain a handle to a parsed set of configuration values. + * + * @param + * 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 get(final SectionParser 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. + *

    + * 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 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 getRawStringList(final String section, + final String subsection, final String name) { + List 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 add(final List curr, final String value) { + if (curr == null) + return Collections.singletonList(value); + if (curr.size() == 1) { + final List r = new ArrayList(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. + * + *

    +	 * [section "subsection"]
    +	 *         name = 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" + * @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. + * + *
    +	 * [section "subsection"]
    +	 *         name = 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" + * @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. + * + *
    +	 * [section "subsection"]
    +	 *         name = 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" + * @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. + * + *
    +	 * [section "subsection"]
    +	 *         name = 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" + * @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 + . emptyList()); + } + + /** + * Set a configuration value. + * + *
    +	 * [section "subsection"]
    +	 *         name = 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" + * @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 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 values) { + final List 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 copy(final State src, final List 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 r = new ArrayList(max); + r.addAll(src.entryList); + return r; + } + + private static int findSectionEnd(final List 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 newEntries = new ArrayList(); + 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. emptyList(), getBaseState()); + } + + private State newState(final List 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. + *

    + * Instances must implement hashCode and equals such that model objects can + * be cached by using the {@code SectionParser} as a key of a HashMap. + *

    + * 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 + * type of the application model created by the parser. + */ + public static interface SectionParser { + /** + * 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> { + 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 parse(Config cfg) { + final Set result = new HashSet(); + 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 entryList; + + final Map cache; + + final State baseState; + + State(List entries, State base) { + entryList = entries; + cache = new ConcurrentHashMap(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 000000000..403d0dbee --- /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 + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * Indicates the associated object is a commit. + *

    + * This constant is fixed and is defined by the Git packfile format. + * + * @see #TYPE_COMMIT + */ + public static final int OBJ_COMMIT = 1; + + /** + * In-pack object type: tree. + *

    + * Indicates the associated object is a tree. + *

    + * This constant is fixed and is defined by the Git packfile format. + * + * @see #TYPE_BLOB + */ + public static final int OBJ_TREE = 2; + + /** + * In-pack object type: blob. + *

    + * Indicates the associated object is a blob. + *

    + * This constant is fixed and is defined by the Git packfile format. + * + * @see #TYPE_BLOB + */ + public static final int OBJ_BLOB = 3; + + /** + * In-pack object type: annotated tag. + *

    + * Indicates the associated object is an annotated tag. + *

    + * This constant is fixed and is defined by the Git packfile format. + * + * @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 + *

    + * 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. + *

    + * 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). + *

    + * This constant is fixed and is defined by the Git packfile format. + */ + public static final int OBJ_OFS_DELTA = 6; + + /** + * In-pack object type: reference delta + *

    + * 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. + *

    + * 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. + *

    + * This constant is fixed and is defined by the Git packfile format. + */ + public static final int OBJ_REF_DELTA = 7; + + /** + * Pack file signature that occurs at file header - identifies file as Git + * packfile formatted. + *

    + * This constant is fixed and is defined by the Git packfile format. + */ + 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. + *

    + * 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 typeString where the parse + * should start. Updated with the new position (just past + * endMark 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 typeString. + */ + 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 000000000..4d4c3a731 --- /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 + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 KEY = new SectionParser() { + 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 000000000..9e1b3da19 --- /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 + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..867aadfb2 --- /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 + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..3616c4107 --- /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 + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..a1843d118 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileBasedConfig.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2009, JetBrains s.r.o. + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * Copyright (C) 2008, Thad Hughes + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * Warning: 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 000000000..4c3fb6b59 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * GIT uses a subset of the available UNIX file permission bits. The + * FileMode class provides access to constants defining the modes + * actually used by GIT. + *

    + */ +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. + *

    + * 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. + *

    + * + * @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. + *

    + * 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 000000000..9ff4deca3 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..6e341d604 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ForceModified.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2009, Jonas Fonseca + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..d62c9df04 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitIndex.java @@ -0,0 +1,976 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2007-2009, Robin Rosenberg + * Copyright (C) 2008, Roger C. Soares + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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:
    path stage flags statdata SHA-1 + *

      + *
    • Path is the relative path in the workdir
    • + *
    • 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.
    • + *
    • flags is the object type and information of validity
    • + *
    • statdata is the size of this object and some other file system specifics, + * some of it ignored by JGit
    • + *
    • SHA-1 represents the content of the references object
    • + *
    + * + * 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 entries = new TreeMap(new Comparator() { + 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 trees = new Stack(); + 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 000000000..a00075912 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2009, Jonas Fonseca + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..3c41e92c4 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexChangedEvent.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..bbcd328b6 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2007-2008, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 added = new HashSet(); + HashSet changed = new HashSet(); + HashSet removed = new HashSet(); + HashSet missing = new HashSet(); + HashSet modified = new HashSet(); + + /** + * @return list of files added to the index, not in the tree + */ + public HashSet getAdded() { + return added; + } + + /** + * @return list of files changed from tree to index + */ + public HashSet getChanged() { + return changed; + } + + /** + * @return list of files removed from index, but in tree + */ + public HashSet getRemoved() { + return removed; + } + + /** + * @return list of files in index, but not filesystem + */ + public HashSet getMissing() { + return missing; + } + + /** + * @return list of files on modified on disk relative to the index + */ + public HashSet 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 000000000..0835b0e52 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexTreeVisitor.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2007-2008, Robin Rosenberg + * Copyright (C) 2006, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..22d958434 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexTreeWalker.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2007-2009, Robin Rosenberg + * Copyright (C) 2006, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..970589884 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 000000000..bf0036bee --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/LockFile.java @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 000000000..478f8ba61 --- /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 + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..d05c8c6b0 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2009, Alex Blewitt + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..9cf1643db --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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 000000000..21b7b9dc9 --- /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. + *

    + * 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. + *

    + * 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 alternates; + + /** Initialize a new database instance for access. */ + protected ObjectDatabase() { + alternates = new AtomicReference(); + } + + /** + * 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. + *

    + * 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? + *

    + * 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. + *

    + * 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. + *

    + * 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 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 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. + *

    + * 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. + *

    + * 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 000000000..297d85f83 --- /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}. + *

    + * 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; + + /** + * Initialize a reference to an on-disk object directory. + * + * @param dir + * the location of the objects 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(NO_PACKS); + } + + /** + * @return the location of the objects 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 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 forReuse = reuseMap(old); + final long lastRead = System.currentTimeMillis(); + final long lastModified = packDirectory.lastModified(); + final Set names = listPackDirectory(); + final List list = new ArrayList(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 reuseMap(final PackList old) { + final Map forReuse = new HashMap(); + 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 listPackDirectory() { + final String[] nameList = packDirectory.list(); + if (nameList == null) + return Collections.emptySet(); + final Set nameSet = new HashSet(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 l = new ArrayList(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 000000000..2e3506619 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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 000000000..bc4072f60 --- /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 + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * This map provides an efficient translation from any ObjectId instance to a + * cached subclass of ObjectId that has the same value. + *

    + * Raw value equality is tested when comparing two ObjectIds (or subclasses), + * not reference equality and not .equals(Object) equality. This + * allows subclasses to override equals to supply their own + * extended semantics. + * + * @param + * type of subclass of ObjectId that will be stored in the map. + */ +public class ObjectIdSubclassMap implements Iterable { + 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. + *

    + * An existing mapping for must not 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 + * + * type of instance to store. + */ + public 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 iterator() { + return new Iterator() { + 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 000000000..97b3b769a --- /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 + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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 must not 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 000000000..60e85eb57 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectWriter.java @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 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 000000000..747f6f122 --- /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. + *

    + * This cache maps a ({@link PackFile},position) tuple to an Object. + *

    + * This cache is suitable for objects that are "relative expensive" to compute + * from the underlying PackFile, given some known position in that file. + *

    + * Whenever a cache miss occurs, {@link #load(PackFile, long)} is invoked by + * exactly one thread for the given (PackFile,position) key tuple. + * This is ensured by an array of locks, with the tuple hashed to a lock + * instance. + *

    + * During a miss, older entries are evicted from the cache so long as + * {@link #isFull()} returns true. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * This cache has an implementation rule such that: + *

      + *
    • {@link #load(PackFile, long)} is invoked by at most one thread at a time + * for a given (PackFile,position) tuple.
    • + *
    • For every load() invocation there is exactly one + * {@link #createRef(PackFile, long, Object)} invocation to wrap a SoftReference + * around the cached entity.
    • + *
    • For every Reference created by createRef() there will be + * exactly one call to {@link #clear(Ref)} to cleanup any resources associated + * with the (now expired) cached entity.
    • + *
    + *

    + * 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. + *

    + * 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 + * type of value stored in the cache. + * @param + * type of {@link OffsetCache.Ref} subclass used by the cache. + */ +abstract class OffsetCache> { + private static final Random rng = new Random(); + + /** ReferenceQueue that {@link #createRef(PackFile, long, Object)} must use. */ + protected final ReferenceQueue 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> 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(); + tableSize = tSize; + clock = new AtomicLong(1); + table = new AtomicReferenceArray>(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 pack 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 e1 = table.get(slot); + V v = scan(e1, pack, position); + if (v != null) + return v; + + synchronized (lock(pack, position)) { + Entry e2 = table.get(slot); + if (e2 != e1) { + v = scan(e2, pack, position); + if (v != null) + return v; + } + + v = load(pack, position); + final Ref ref = createRef(pack, position, v); + hit(ref); + for (;;) { + final Entry n = new Entry(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 n, final PackFile pack, final long position) { + for (; n != null; n = n.next) { + final Ref 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 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 old = null; + int slot = 0; + for (int b = evictBatch - 1; b >= 0; b--, ptr++) { + if (tableSize <= ptr) + ptr = 0; + for (Entry 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 e1 = table.get(slot); + table.compareAndSet(slot, e1, clean(e1)); + } + } + } + + /** + * Clear every entry from the cache. + *

    + * 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 e1; + do { + e1 = table.get(s); + for (Entry e = e1; e != null; e = e.next) + e.kill(); + } while (!table.compareAndSet(s, e1, null)); + } + gc(); + } + + /** + * Clear all entries related to a single file. + *

    + * 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 e1 = table.get(s); + boolean hasDead = false; + for (Entry 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. + *

    + * 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 + * (pack,position), but multiple threads can call this method + * concurrently for different (pack,position) 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. + *

    + * 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. + *

    + * Implementors MUST 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 v. + */ + @SuppressWarnings("unchecked") + protected R createRef(final PackFile pack, final long position, final V v) { + return (R) new Ref(pack, position, v, queue); + } + + /** + * Update accounting information now that an object has left the cache. + *

    + * 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 ref.get() to return null. + */ + protected void clear(final R ref) { + // Do nothing by default. + } + + /** + * Determine if the cache is full and requires eviction of entries. + *

    + * 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 e1 = table.get(s); + for (Entry 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 (PackFile,position) tuple. + *

    + * For example, return packHash + (int) (position >>> 4). + * 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 Entry clean(Entry top) { + while (top != null && top.dead) { + top.ref.enqueue(); + top = top.next; + } + if (top == null) + return null; + final Entry n = clean(top.next); + return n == top.next ? top : new Entry(n, top.ref); + } + + private static class Entry { + /** Next entry in the hash table's chain list. */ + final Entry next; + + /** The referenced object. */ + final Ref ref; + + /** + * Marked true when ref.get() returns null and the ref is dead. + *

    + * 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 n, final Ref r) { + next = n; + ref = r; + } + + final void kill() { + dead = true; + ref.enqueue(); + } + } + + /** + * A soft reference wrapped around a cached object. + * + * @param + * type of the cached object. + */ + protected static class Ref extends SoftReference { + final PackFile pack; + + final long position; + + long lastAccess; + + private boolean cleared; + + protected Ref(final PackFile pack, final long position, final V v, + final ReferenceQueue 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 000000000..9defcad91 --- /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 + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 { + /** Sorts PackFiles to be most recently created to least recently created. */ + public static Comparator SORT = new Comparator() { + 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 .idx file listing the contents. + * @param packFile + * path of the .pack 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. + *

    + * For performance reasons only the index file is searched; the main pack + * content is ignored entirely. + *

    + * + * @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. + *

    + * Iterator returns objects in SHA-1 lexicographical order. + *

    + * + * @return iterator over entries of associated pack index + * + * @see PackIndex#iterator() + */ + public Iterator iterator() { + try { + return idx().iterator(); + } catch (IOException e) { + return Collections. 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 000000000..733834e5b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndex.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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}. + *

    + * 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. + *

    + */ +public abstract class PackIndex implements Iterable { + /** + * Open an existing pack .idx file for reading. + *

    + * 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. + *

    + * + * @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. + *

    + * Iterator returns objects in SHA-1 lexicographical order. + *

    + * + * @return iterator over pack index entries + */ + public abstract Iterator 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()}. + *

    + * This method is a constant-time replacement for the following loop: + * + *

    +	 * Iterator<MutableEntry> eItr = index.iterator();
    +	 * int curPosition = 0;
    +	 * while (eItr.hasNext() && curPosition++ < nthPosition)
    +	 * 	eItr.next();
    +	 * ObjectId result = eItr.next().toObjectId();
    +	 * 
    + * + * @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()}. + *

    + * This method is a constant-time replacement for the following loop: + * + *

    +	 * Iterator<MutableEntry> eItr = index.iterator();
    +	 * int curPosition = 0;
    +	 * while (eItr.hasNext() && curPosition++ < nthPosition)
    +	 * 	eItr.next();
    +	 * ObjectId result = eItr.next().toObjectId();
    +	 * 
    + * + * @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 { + 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 000000000..a7bf99e2f --- /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 + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 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 000000000..c37ce646d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexV2.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 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 000000000..5fcf71a78 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriter.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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}. + *

    + * Pack index files (the .idx 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. + *

    + * 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. + *

    + * 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 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 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. + *

    + * 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 toStore, + final byte[] packDataChecksum) throws IOException { + entries = toStore; + packChecksum = packDataChecksum; + writeImpl(); + out.flush(); + } + + /** + * Writes the index file to {@link #out}. + *

    + * Implementations should go something like: + * + *

    +	 * writeFanOutTable();
    +	 * for (final PackedObjectInfo po : entries)
    +	 * 	writeOneEntry(po);
    +	 * writeChecksumFooter();
    +	 * 
    + * + *

    + * Where the logic for writeOneEntry 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. + *

    + * 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. + *

    + * 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. + *

    + * The standard footer contains two checksums (20 byte SHA-1 values): + *

      + *
    1. Pack data checksum - taken from the last 20 bytes of the pack file.
    2. + *
    3. Index data checksum - checksum of all index bytes written, including + * the pack data checksum above.
    4. + *
    + * + * @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 000000000..b3be5480c --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV1.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..b6ac7b89e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackIndexWriterV2.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..de8e3fa63 --- /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 .keep file. */ +public class PackLock { + private final File keepFile; + + /** + * Create a new lock for a pack file. + * + * @param packFile + * location of the pack-*.pack 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 pack-*.keep 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 .keep 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 000000000..a348f1e54 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..c0ed7b29a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PackReverseIndex.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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; + +/** + *

    + * 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. + *

    + * + * @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 000000000..6162deab7 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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; + +/** + *

    + * PackWriter class is responsible for generating pack files from specified set + * of objects from repository. This implementation produce pack files in format + * version 2. + *

    + *

    + * Source of objects may be specified in two ways: + *

      + *
    • (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
    • + *
    • by providing iterator of {@link RevObject} specifying exact list and + * order of objects in pack
    • + *
    + * 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)}. + *

    + *

    + * Class provide set of configurable options and {@link ProgressMonitor} + * support, as operations may take a long time for big repositories. Deltas + * searching algorithm is NOT IMPLEMENTED yet - this implementation + * relies only on deltas and objects reuse. + *

    + *

    + * 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. + *

    + */ + +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 objectsLists[] = new List[Constants.OBJ_TAG + 1]; + { + objectsLists[0] = Collections. emptyList(); + objectsLists[Constants.OBJ_COMMIT] = new ArrayList(); + objectsLists[Constants.OBJ_TREE] = new ArrayList(); + objectsLists[Constants.OBJ_BLOB] = new ArrayList(); + objectsLists[Constants.OBJ_TAG] = new ArrayList(); + } + + private final ObjectIdSubclassMap objectsMap = new ObjectIdSubclassMap(); + + // edge objects for thin packs + private final ObjectIdSubclassMap edgeObjects = new ObjectIdSubclassMap(); + + 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 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. + *

    + * 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. + *

    + * 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. + *

    + * Default setting: {@value #DEFAULT_REUSE_DELTAS} + *

    + * + * @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. + *

    + * When raw delta data is directly copied from a pack file, checksum is + * computed to verify data. + *

    + *

    + * Default setting: {@value #DEFAULT_REUSE_DELTAS} + *

    + * + * @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. + *

    + * Default setting: {@value #DEFAULT_REUSE_OBJECTS} + *

    + * + * @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. + *

    + * Default setting: {@value #DEFAULT_REUSE_OBJECTS} + *

    + * + * @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). + *

    + * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET} + *

    + * + * @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). + *

    + * Default setting: {@value #DEFAULT_DELTA_BASE_AS_OFFSET} + *

    + * + * @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. + *

    + * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH} + *

    + * + * @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. + *

    + * Default setting: {@value #DEFAULT_MAX_DELTA_DEPTH} + *

    + * + * @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. + *

    + * Iterator exactly 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. + *

    + *

    + * 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. + *

    + * + * @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 objectsSource) + throws IOException { + while (objectsSource.hasNext()) { + addObject(objectsSource.next()); + } + } + + /** + * Prepare the list of objects to be written to the pack stream. + *

    + * 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. + *

    + * + * @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 interestingObjects, + final Collection 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. + *

    + * 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 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 sortByName() { + if (sortedByName == null) { + sortedByName = new ArrayList(objectsMap.size()); + for (List list : objectsLists) { + for (ObjectToPack otp : list) + sortedByName.add(otp); + } + Collections.sort(sortedByName); + } + return sortedByName; + } + + /** + * Write the prepared pack to the supplied stream. + *

    + * 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. + *

    + *

    + * All reused objects data checksum (Adler32/CRC32) is computed and + * validated against existing checksum. + *

    + * + * @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 reuseLoaders = new ArrayList(); + for (List 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 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 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 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 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(), 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 interestingObjects, + final Collection 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. + *

    + * 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: + *

      + *
    • 1 bit: wantWrite
    • + *
    • 3 bits: type
    • + *
    • 28 bits: deltaDepth
    • + *
    + */ + private int flags; + + /** + * Construct object for specified object id.
    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 000000000..4125579b2 --- /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 + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

      + *
    • {@link #getType()}
    • + *
    • {@link #getSize()}
    • + *
    • {@link #getBytes()}, {@link #getCachedBytes}
    • + *
    • {@link #getRawSize()}
    • + *
    • {@link #getRawType()}
    • + *
    + * + * @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. + *

    + * Applications trying to copy raw pack data should ensure the pack stays + * open and available throughout the entire copy. To do that use: + * + *

    +	 * loader.beginCopyRawData();
    +	 * try {
    +	 * 	loader.copyRawData(out, tmpbuf, curs);
    +	 * } finally {
    +	 * 	loader.endCopyRawData();
    +	 * }
    +	 * 
    + * + * @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. + *

    + * Copied data doesn't include object header. User must provide temporary + * buffer used during copying by underlying I/O layer. + *

    + * + * @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 000000000..a9f520e8f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..cab1b0858 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ProgressMonitor.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * This is an incremental update; if invoked once per work unit the correct + * value for our argument is 1, 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 000000000..b040e9bed --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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, ...). + *

    + * 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. + *

    + * Creation is likely to choose {@link #LOOSE} storage. + */ + NEW(true, false), + + /** + * The ref is stored in a file by itself. + *

    + * Updating this ref affects only this ref. + */ + LOOSE(true, false), + + /** + * The ref is stored in the packed-refs file, with + * others. + *

    + * 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}. + *

    + * 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. + *

    + * 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 ref^{} (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? + *

    + * 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 000000000..cbbc0a91c --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2008, Charles O'Farrell + * 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. + *

    + * Useful for command line tools or writing out refs to file. + */ +public class RefComparator implements Comparator { + + /** 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 sort(final Collection refs) { + final List r = new ArrayList(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 000000000..2c68dbb6d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java @@ -0,0 +1,522 @@ +/* + * Copyright (C) 2007-2009, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 looseRefs; + private Map looseRefsMTime; + private Map looseSymRefs; + + private final File packedRefsFile; + + private Map 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(); + looseRefsMTime = new HashMap(); + packedRefs = new HashMap(); + looseSymRefs = new HashMap(); + 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 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 getTags() { + final Map tags = new HashMap(); + 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 readRefs() { + final HashMap avail = new HashMap(); + 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 avail) { + refreshPackedRefs(); + avail.putAll(packedRefs); + } + + private void readLooseRefs(final Map 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(); + return; + } + + final Map newPackedRefs = new HashMap(); + 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 000000000..d41bbb644 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefLogWriter.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2007-2009, Robin Rosenberg + * Copyright (C) 2006, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..7e76ac58a --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..18dc582c8 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java @@ -0,0 +1,642 @@ +/* + * Copyright (C) 2008, Charles O'Farrell + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * Has no meaning for update. + */ + REJECTED_CURRENT_BRANCH, + + /** + * The ref was probably not updated/deleted because of I/O error. + *

    + * Unexpected I/O error occurred when writing new ref. Such error may + * result in uncertain state, but most probably ref was not updated. + *

    + * This kind of error doesn't include {@link #LOCK_FAILURE}, which is a + * different case. + */ + IO_FAILURE, + + /** + * The ref was renamed from another name + *

    + */ + 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * Merge test will be performed according to {@link #isForceUpdate()}. + *

    + * This is the same as: + * + *

    +	 * return update(new RevWalk(repository));
    +	 * 
    + * + * @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. + *

    + * 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. + *

    + * This is the same as: + * + *

    +	 * return delete(new RevWalk(repository));
    +	 * 
    + * + * @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 000000000..34e73a3f7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2008, Charles O'Farrell + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2009, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 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 refs) { + this.refs = RefComparator.sort(refs); + } + + /** + * Rebuild the {@link Constants#INFO_REFS}. + *

    + * 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. + *

    + * 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 000000000..a85eb0e4c --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 entries = getReverseEntries(1); + return entries.size() > 0 ? entries.get(0) : null; + } + + /** + * @return all reflog entries in reverse order + * @throws IOException + */ + public List 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 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 ret = new ArrayList(); + 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 000000000..7a7d99cd2 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefsChangedEvent.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..181ca580f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -0,0 +1,1176 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2006-2009, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + * + *

      + *
    • GIT_DIR + *
        + *
      • objects/ - objects
      • + *
      • refs/ - tags and heads
      • + *
      • config - configuration
      • + *
      • info/ - more configurations
      • + *
      + *
    • + *
    + *

    + * This class is thread-safe. + *

    + * 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 listeners = new Vector(); // thread safe + static private final List allListeners = new Vector(); // 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 openObjectInAllPacks( + final AnyObjectId objectId, final WindowCursor curs) + throws IOException { + Collection result = new LinkedList(); + 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 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. + *

      + *
    • SHA-1 - a SHA-1
    • + *
    • refs/... - a ref name
    • + *
    • ref^n - nth parent reference
    • + *
    • ref~n - distance via parent reference
    • + *
    • ref@{n} - nth version of ref
    • + *
    • ref^{tree} - tree references by ref
    • + *
    • ref^{commit} - commit references by ref
    • + *
    + * + * Not supported is + *
      + *
    • timestamps in reflogs, ref@{full or relative timestamp}
    • + *
    • abbreviated SHA-1's
    • + *
    + * + * @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 parents.length) + refId = null; + else + refId = parents[pnum - 1]; + } + i = j - 1; + break; + case '{': + int k; + String item = null; + for (k=i+2; k 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 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 getTags() { + return refs.getTags(); + } + + /** + * Peel a possibly unpeeled ref and updates it. + *

    + * 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 ref if ref.isPeeled() 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> getAllRefsByPeeledObjectId() { + Map allRefs = getAllRefs(); + Map> ret = new HashMap>(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 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(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 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 all; + synchronized (listeners) { + all = new ArrayList(listeners); + } + synchronized (allListeners) { + all.addAll(allListeners); + } + for (final RepositoryListener l : all) { + l.refsChanged(event); + } + } + } + + void fireIndexChanged() { + final IndexChangedEvent event = new IndexChangedEvent(this); + List all; + synchronized (listeners) { + all = new ArrayList(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 000000000..e43c33ad7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryAdapter.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..e8630a3c6 --- /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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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> cacheMap; + + private final Lock[] openLocks; + + private RepositoryCache() { + cacheMap = new ConcurrentHashMap>(); + 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 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(db); + cacheMap.put(location, ref); + } + } + } + db.incrementOpen(); + return db; + } + + private void registerRepository(final Key location, final Repository db) { + db.incrementOpen(); + SoftReference newRef = new SoftReference(db); + Reference oldRef = cacheMap.put(location, newRef); + Repository oldDb = oldRef != null ? oldRef.get() : null; + if (oldDb != null) + oldDb.close(); + } + + private void unregisterRepository(final Key location) { + Reference 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>> i = cacheMap + .entrySet().iterator(); i.hasNext();) { + final Map.Entry> 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * The method performs some basic guessing to locate the repository. + * Searched paths are: + *

      + *
    1. {@code directory} // assume exact match
    2. + *
    3. {@code directory} + "/.git" // assume working directory
    4. + *
    5. {@code directory} + ".git" // assume bare
    6. + *
    + * + * @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. + *

    + * 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. + *

    + * The method performs some basic guessing to locate the repository. + * Searched paths are: + *

      + *
    1. {@code directory} // assume exact match
    2. + *
    3. {@code directory} + "/.git" // assume working directory
    4. + *
    5. {@code directory} + ".git" // assume bare
    6. + *
    + * + * @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 000000000..495049ce7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryChangedEvent.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..805975a8d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryConfig.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2009, JetBrains s.r.o. + * Copyright (C) 2007-2008, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * Copyright (C) 2008, Thad Hughes + * Copyright (C) 2009, Yann Simon + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..0473093e2 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryListener.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * It currently does not 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 000000000..6159839b1 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2008, Mike Ralphson + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..81666be45 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..0e1c1651d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tag.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2006-2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..a668b11be --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..a745cbecd --- /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 KEY = new SectionParser() { + 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 000000000..2b8a0e7cf --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java @@ -0,0 +1,608 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2007-2008, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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= 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. + * + *

    + * 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= 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 000000000..b6dd9311a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2007-2008, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..937baf6cc --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeIterator.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2006, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 { + + 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 000000000..174551546 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeVisitor.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2009, Jonas Fonseca + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..680bab6be --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeVisitorWithCurrentDirectory.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2007-2009, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * Copyright (C) 2009, Vasyl' Vavrychuk + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 stack = new ArrayList(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 000000000..7da14172e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Treeish.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..3cef48242 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectCache.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 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(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(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 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 000000000..c31dfdee4 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/UnpackedObjectLoader.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..28b3cd027 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 KEY = new SectionParser() { + 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 000000000..31439d489 --- /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 + * Copyright (C) 2006-2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..9c2134263 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 { + 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. + *

    + * 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. + *

    + * 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 { + final int size; + + WindowRef(final PackFile pack, final long position, final ByteWindow v, + final ReferenceQueue 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 000000000..2d8aef34c --- /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. Default is 128. + */ + 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. Default is 10 MB. + */ + 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. Default is 8 KB. + */ + 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. + * Default false. + */ + 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. + * Default 10 MB. + */ + 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. + *

    + * 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 000000000..fcf43adf6 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 dstbuf 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 + * pos. + * @return number of bytes actually copied; this may be less than + * cnt if cnt 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 dstbuf to inflate into. + * @return updated dstoff based on the number of bytes + * successfully inflated into dstbuf. + * @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 000000000..75cc3bdc5 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WorkDirCheckout.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Roger C. Soares + * Copyright (C) 2006, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 true, will scan first to see if it's possible to check out, + * otherwise throw {@link CheckoutConflictException}. If false, + * 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 entry : updated.entrySet()) { + Entry newEntry = index.addEntry(merge.findBlobMember(entry.getKey())); + index.checkoutEntry(root, newEntry); + } + } + + ArrayList conflicts = new ArrayList(); + ArrayList removed = new ArrayList(); + + Tree head = null; + + HashMap updated = new HashMap(); + + 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 listFiles(File file) { + ArrayList list = new ArrayList(); + listFiles(file, list); + return list; + } + + private void listFiles(File dir, ArrayList 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 getConflicts() { + return conflicts; + } + + /** + * @return a list of all files removed by this checkout + */ + public ArrayList 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 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 000000000..5bb4e535e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/WriteTree.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2009, Jonas Fonseca + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2006-2007, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..e7d84c68a --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 STRATEGIES = new HashMap(); + + 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 000000000..275a6d68f --- /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. + *

    + * 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. + *

    + * 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 000000000..c941af948 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 000000000..6cd244599 --- /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. + *

    + * 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). + *

    + * 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 000000000..343d8973e --- /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 000000000..bb23d0ee8 --- /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. + *

    + * 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 000000000..340b67456 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..e95c026dd --- /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. + *

    + * 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 getHunks() { + return (List) 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 {@link #getParentCount()} + 1 + * 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 ids = new ArrayList(); + 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 000000000..781190539 --- /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 000000000..dece17e4c --- /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 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. + *

    + * 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. + *

    + * The meaning of the old name can differ depending on the semantic meaning + * of this patch: + *

      + *
    • file add: always /dev/null
    • + *
    • file modify: always {@link #getNewName()}
    • + *
    • file delete: always the file being deleted
    • + *
    • file copy: source file the copy originates from
    • + *
    • file rename: source file the rename originates from
    • + *
    + * + * @return old name for this file. + */ + public String getOldName() { + return oldName; + } + + /** + * Get the new name associated with this file. + *

    + * The meaning of the new name can differ depending on the semantic meaning + * of this patch: + *

      + *
    • file add: always the file being created
    • + *
    • file modify: always {@link #getOldName()}
    • + *
    • file delete: always /dev/null
    • + *
    • file copy: destination file the copy ends up at
    • + *
    • file rename: destination file the rename ends up at/li> + *
    + * + * @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 index. + * + * @return the object id; null if there is no index line + */ + public AbbreviatedObjectId getOldId() { + return oldId; + } + + /** + * Get the new object id from the index. + * + * @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 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(); + 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 ( + * buf.length) 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 000000000..13046137d --- /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 000000000..9d78d0b99 --- /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 000000000..1eff3edd8 --- /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 files; + + /** Formatting errors, if any were identified. */ + private final List errors; + + /** Create an empty patch. */ + public Patch() { + files = new ArrayList(); + errors = new ArrayList(0); + } + + /** + * Add a single file to this patch. + *

    + * 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 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 getErrors() { + return errors; + } + + /** + * Parse a patch received from an InputStream. + *

    + * 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[]. + *

    + * 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 end - ptr. + */ + 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 000000000..f872ae0a4 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/AbstractPlotRenderer.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * Subclasses should call {@link #paintCommit(PlotCommit, int)} after they have + * otherwise configured their instance to draw one commit into the current + * location. + *

    + * 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 + * type of lane being used by the application. + * @param + * type of color object used by the graphics library. + */ +public abstract class AbstractPlotRenderer { + 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 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 p; + final TLane pLane; + final TColor pColor; + final int cx; + + p = (PlotCommit) 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. + *

    + * Colors returned by this method will be passed to the other drawing + * primitives, so the color returned should be application specific. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 000000000..54d7c013d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 + * type of lane being used by the plotter. + * @see PlotCommitList + */ +public class PlotCommit 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 000000000..7c27a86f4 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * Commits are allocated into lanes as they enter the list, based upon their + * connections between descendant (child) commits and ancestor (parent) commits. + *

    + * The source of the list must be a {@link PlotWalk} and {@link #fillTo(int)} + * must be used to populate the list. + * + * @param + * type of lane used by the application. + */ +public class PlotCommitList extends + RevCommitList> { + static final int MAX_LENGTH = 25; + + private int lanesAllocated; + + private final TreeSet freeLanes = new TreeSet(); + + private HashSet activeLanes = new HashSet(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. + *

    + * 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. + *

    + * 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 currCommit, + final Collection result) { + for (final PlotLane p : currCommit.passingLanes) + result.add((L) p); + } + + @Override + protected void enter(final int index, final PlotCommit 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 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 000000000..45dd9960d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotLane.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 000000000..bebe148eb --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2008-2009, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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> 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 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 { + 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 000000000..30d29a80b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 000000000..371cd06dd --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockObjQueue.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..5e7a7998e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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 000000000..6be0c8584 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..6ce63fe16 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..4a0d19d60 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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 000000000..627e1c7a5 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/EndGenerator.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..5690a5d86 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..9d734a729 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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}. + *

    + * 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 000000000..97a8ab2ad --- /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 000000000..541f2748e --- /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". + *

    + * 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. + *

    + * If there is an email address looking string inside of angle brackets + * (e.g. ""), 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 000000000..de9fabc19 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/Generator.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 000000000..9abaf8dcc --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..2f01f541d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * This generator is selected if the RevFilter is only + * {@link org.eclipse.jgit.revwalk.filter.RevFilter#MERGE_BASE}. + *

    + * 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. + *

    + * 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 000000000..d8f88ea30 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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()}. + *

    + * 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}. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 000000000..e723bce51 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 000000000..f4d46e7e6 --- /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 + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..1d2a49d3a --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 (NOTE - THIS IS NOT A COPY). + *

    + * 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 (NOTE - THIS IS NOT A COPY). + *

    + * 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 NOT A COPY. + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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: + *

      + *
    • {@code Signed-off-by} (agrees to Developer Certificate of Origin) + *
    • {@code Acked-by} (thinks change looks sane in context) + *
    • {@code Reported-by} (originally found the issue this change fixes) + *
    • {@code Tested-by} (validated change fixes the issue for them) + *
    • {@code CC}, {@code Cc} (copy on all email related to this change) + *
    • {@code Bug} (link to project's bug tracking system) + *
    + * + * @return ordered list of footer lines; empty list if no footers found. + */ + public final List 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 r = new ArrayList(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 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 getFooterLines(final FooterKey keyName) { + final List src = getFooterLines(); + if (src.isEmpty()) + return Collections.emptyList(); + final ArrayList r = new ArrayList(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. + *

    + * Subclasses must call super.reset() 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 000000000..d6abccfba --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitList.java @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 + * type of subclass of RevCommit the list is storing. + */ +public class RevCommitList extends RevObjectList { + private RevWalk walker; + + @Override + public void clear() { + super.clear(); + walker = null; + } + + /** + * Apply a flag to all commits matching the specified filter. + *

    + * Same as applyFlag(matching, flag, 0, size()), 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. + *

    + * 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 rangeBegin 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. + *

    + * Same as clearFlag(flag, 0, size()), 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. + *

    + * This method is actually implemented in terms of: + * applyFlag(RevFilter.NONE, flag, rangeBegin, rangeEnd). + * + * @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 rangeBegin 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 begin + * 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 size()-1, to test the last commit in the + * list. + * @return index of the first commit at or before index begin + * 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. + *

    + * 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. + *

    + * 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 000000000..a8d644c33 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * To create a flag use {@link RevWalk#newFlag(String)}. + */ +public class RevFlag { + /** + * Uninteresting by {@link RevWalk#markUninteresting(RevCommit)}. + *

    + * 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. + *

    + * 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 000000000..fb9b4525a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlagSet.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 { + int mask; + + private final List active; + + /** Create an empty set of flags. */ + public RevFlagSet() { + active = new ArrayList(); + } + + /** + * 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(s.active); + } + + /** + * Create a set of flags, copied from an existing collection. + * + * @param s + * the collection to copy flags from. + */ + public RevFlagSet(final Collection 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 iterator() { + final Iterator i = active.iterator(); + return new Iterator() { + 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 000000000..e5e3abcad --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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 000000000..3ae1a71f1 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 + * type of subclass of RevObject the list is storing. + */ +public class RevObjectList extends AbstractList { + 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 000000000..238af12fd --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevSort.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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). + *

    + * This strategy can be combined with {@link #TOPO}. + */ + COMMIT_TIME_DESC, + + /** + * Topological sorting (all children before parents). + *

    + * This strategy can be combined with {@link #COMMIT_TIME_DESC}. + */ + TOPO, + + /** + * Flip the output into the reverse ordering. + *

    + * 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. + *

    + * 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 000000000..a77757dc2 --- /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 + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 000000000..4fa153a65 --- /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 + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..ac2643422 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java @@ -0,0 +1,1085 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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). + *

    + * 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 { + /** + * Set on objects whose important header data has been loaded. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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}. + *

    + * 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 objects; + + private int freeFlags = APP_FLAGS; + + private int delayFreeFlags; + + int carryFlags = UNINTERESTING; + + private final ArrayList roots; + + AbstractRevQueue queue; + + Generator pending; + + private final EnumSet 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(); + roots = new ArrayList(); + 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. + *

    + * 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. + *

    + * 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 list) + throws MissingObjectException, IncorrectObjectTypeException, + IOException { + for (final RevCommit c : list) + markStart(c); + } + + /** + * Mark a commit to not produce in the output. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * A commit base is an ancestor of tip if we + * can find a path of commits that leads from tip and ends at + * base. + *

    + * 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 tip. + * @param tip + * commit to start iteration from, and which is most likely a + * descendant (child) of base. + * @return true if there is a path directly from tip to + * base (and thus base is fully merged + * into tip); 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 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. + *

    + * 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. + *

    + * 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. + *

    + * Multiple filters may be combined by constructing an arbitrary tree of + * AndRevFilter or OrRevFilter instances to + * describe the boolean expression required by the application. Custom + * filter implementations may also be constructed by applications. + *

    + * 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. + *

    + * If null or {@link TreeFilter#ALL} the path limiter is removed. Commits + * will not be simplified. + *

    + * 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. + *

    + * 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? + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 set) { + for (final RevFlag flag : set) + carry(flag); + } + + /** + * Allow a flag to be recycled for a different use. + *

    + * Recycled flags always come back as a different Java object instance when + * assigned again by {@link #newFlag(String)}. + *

    + * 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. + *

    + * 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. + *

    + * 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 not 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. + *

    + * 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 not 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. + *

    + * 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 not 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 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 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 000000000..04d8def43 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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 000000000..4cec8a7f0 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteTreeFilter.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * This filter is ANDed to evaluate after all other filters and ties the + * configured {@link TreeFilter} into the revision walking process. + *

    + * 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 000000000..c5353fe8c --- /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 + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 000000000..78c0d8f16 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 000000000..406a7764d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AndRevFilter.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 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 000000000..2ede91b57 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/AuthorRevFilter.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * An optimized substring search may be automatically selected if the + * pattern does not contain any regular expression meta-characters. + *

    + * 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 000000000..a70ff9fd6 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2009, Mark Struberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 ts. + */ + 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 ts. + */ + 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 since + * and before or equal a given date/time until. + * + * @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 000000000..59c3e080d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitterRevFilter.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * An optimized substring search may be automatically selected if the + * pattern does not contain any regular expression meta-characters. + *

    + * 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 000000000..6ab3b1d3b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MessageRevFilter.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * An optimized substring search may be automatically selected if the + * pattern does not contain any regular expression meta-characters. + *

    + * 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 000000000..117378c9f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/NotRevFilter.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 a. + */ + 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 000000000..a2782763b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/OrRevFilter.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 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 000000000..5f2bcf26a --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 000000000..2d67d9763 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 AndRevFilter and + * OrRevFilter to create complex boolean expressions. + *

    + * Applications should install the filter on a RevWalk by + * {@link RevWalk#setRevFilter(RevFilter)} prior to starting traversal. + *

    + * 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. + * + *

    + * Message filters: + *

      + *
    • Author name/email: {@link AuthorRevFilter}
    • + *
    • Committer name/email: {@link CommitterRevFilter}
    • + *
    • Message body: {@link MessageRevFilter}
    • + *
    + * + *

    + * Merge filters: + *

      + *
    • Skip all merges: {@link #NO_MERGES}.
    • + *
    + * + *

    + * Boolean modifiers: + *

      + *
    • AND: {@link AndRevFilter}
    • + *
    • OR: {@link OrRevFilter}
    • + *
    • NOT: {@link NotRevFilter}
    • + *
    + */ +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). + *

    + * 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. + *

    + * 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 000000000..8339fc7c0 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 a. + */ + 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 a. + */ + 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 a. + */ + 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 a. + */ + 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 a. + */ + 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 000000000..c6f421784 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SubStringRevFilter.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..9343f4aba --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java @@ -0,0 +1,795 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * Authentication is always performed using the user's AWSAccessKeyId and their + * private AWSSecretAccessKey. + *

    + * Optional client-side encryption may be enabled if requested. The format is + * compatible with jets3t, + * 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 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(); + 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 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 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. + *

    + * The connection properties are a subset of those supported by the popular + * jets3t library. + * For example: + * + *

    +	 * # AWS Access and Secret Keys (required)
    +	 * accesskey: <YourAWSAccessKey>
    +	 * secretkey: <YourAWSSecretKey>
    +	 *
    +	 * # 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: <encryption pass-phrase>
    +	 * crypto.algorithm: PBEWithMD5AndDES
    +	 * 
    + * + * @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. + *

    + * 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 prefix, after removing + * prefix (or prefix + "/")from all + * of them. + * @throws IOException + * sending the request was not possible, or the response XML + * document could not be parsed properly. + */ + public List 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. + *

    + * 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. + *

    + * This form is only suitable for smaller contents, where the caller can + * reasonable fit the entire thing into memory. + *

    + * 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. + *

    + * 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. + *

    + * Network transmission is attempted only when close() gets + * called at the end of output. Closing the returned stream can therefore + * take significant time, especially if the written content is very large. + *

    + * 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 noArgs = Collections.emptyMap(); + return open(method, bucket, key, noArgs); + } + + private HttpURLConnection open(final String method, final String bucket, + final String key, final Map 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> i; + + urlstr.append('?'); + i = args.entrySet().iterator(); + while (i.hasNext()) { + final Map.Entry 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> reqHdr = c.getRequestProperties(); + final SortedMap sigHdr = new TreeMap(); + for (final Map.Entry> 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 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 entries = new ArrayList(); + + 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 args = new TreeMap(); + 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 000000000..14be1700c --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 advertisedRefs = Collections.emptyMap(); + + private boolean startedOperation; + + public Map getRefsMap() { + return advertisedRefs; + } + + public final Collection 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. + *

    + * 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 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 000000000..b77e644a2 --- /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 + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * Implementors of fetch over pack-based protocols should consider using + * {@link BasePackFetchConnection} instead. + *

    + */ +abstract class BaseFetchConnection extends BaseConnection implements + FetchConnection { + public final void fetch(final ProgressMonitor monitor, + final Collection want, final Set 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 want, final Set 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 000000000..bd86ec047 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 remoteCapablities = new HashSet(); + + /** Extra objects the remote has, but which aren't offered as refs. */ + protected final Set additionalHaves = new HashSet(); + + 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 avail = new LinkedHashMap(); + 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 000000000..3f478680a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java @@ -0,0 +1,538 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 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(); + 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 KEY = new SectionParser() { + 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 want, final Set 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 getPackLocks() { + if (packLock != null) + return Collections.singleton(packLock); + return Collections. emptyList(); + } + + protected void doFetch(final ProgressMonitor monitor, + final Collection want, final Set 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 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 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 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 000000000..b1ce28d35 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * This implementation honors {@link Transport#isPushThin()} option. + *

    + * 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 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 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 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 refUpdates, + final ProgressMonitor monitor) throws IOException { + final PackWriter writer = new PackWriter(local, monitor); + final ArrayList remoteObjects = new ArrayList( + getRefs().size()); + final ArrayList newObjects = new ArrayList( + 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 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 000000000..a0d172e0f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2009, Matthias Sohn + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 prereqs = new HashSet(); + + 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 avail = new LinkedHashMap(); + 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 want, final Set 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 getPackLocks() { + if (packLock != null) + return Collections.singleton(packLock); + return Collections. 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 missing = new ArrayList(); + final List commits = new ArrayList(); + 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 000000000..92d07e128 --- /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. + *

    + * 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}. + *

    + * Applications creating bundles need to call one or more include + * 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. + *

    + * Optional assume 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 include; + + private final Set 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(); + assume = new HashSet(); + } + + /** + * 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. + *

    + * This is a utility function for: + * include(r.getName(), r.getObjectId()). + * + * @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. + *

    + * 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. + *

    + * 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 inc = new HashSet(); + final HashSet exc = new HashSet(); + 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 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 000000000..fbed0693c --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 getRefsMap(); + + /** + * Get the complete list of refs advertised as available for fetching or + * pushing. + *

    + * 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 getRefs(); + + /** + * Get a single advertised ref by name. + *

    + * The name supplied should be valid ref name. To get a peeled value for a + * ref (aka refs/tags/v1.0^{}) use the base name (without + * the ^{} 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. + *

    + * 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 000000000..c6f69043b --- /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 git:// 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 exports; + + private Collection 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(); + exportBase = new CopyOnWriteArrayList(); + 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 git-daemon-export-ok is required to export + * a repository; true if git-daemon-export-ok is + * ignored. + * @see #setExportAll(boolean) + */ + public boolean isExportAll() { + return exportAll; + } + + /** + * Set whether or not to export all repositories. + *

    + * If false (the default), repositories must have a + * git-daemon-export-ok file to be accessed through this + * daemon. + *

    + * If true, all repositories are available through the daemon, whether or + * not git-daemon-export-ok exists. + * + * @param export + */ + public void setExportAll(final boolean export) { + exportAll = export; + } + + /** + * Add a single repository to the set that is exported by this daemon. + *

    + * The existence (or lack-thereof) of git-daemon-export-ok 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 git-daemon-export-ok 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 000000000..0b8de0343 --- /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 000000000..2b9495798 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 git://. */ +public abstract class DaemonService { + private final String command; + + private final SectionParser configKey; + + private boolean enabled; + + private boolean overridable; + + DaemonService(final String cmdName, final String cfgName) { + command = cmdName.startsWith("git-") ? cmdName : "git-" + cmdName; + configKey = new SectionParser() { + 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 daemon.servicename 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 000000000..6ff3d4b2f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 $HOME/.ssh. + *

    + * 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. + *

    + * 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 000000000..dea0f2dc1 --- /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 + * Copyright (C) 2008, Mike Ralphson + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * A fetch connection typically connects to the git-upload-pack + * 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. + *

    + * 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. + *

    + * 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. + *

    + * Only one call per connection is allowed. Subsequent calls will result in + * {@link TransportException}. + *

    + *

    + * 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. + *

    + * + * @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 want, final Set have) + throws TransportException; + + /** + * Did the last {@link #fetch(ProgressMonitor, Collection, Set)} get tags? + *

    + * 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. + *

    + * 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? + *

    + * 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. + *

    + * 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. + *

    + * Callers that set a lock message must 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 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 000000000..e2ec71030 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchHeadRecord.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2008, Charles O'Farrell + * Copyright (C) 2009, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..65a5b1769 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java @@ -0,0 +1,451 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 toFetch; + + /** Set of refs we will actually wind up asking to obtain. */ + private final HashMap askFor = new HashMap(); + + /** Objects we know we have locally. */ + private final HashSet have = new HashSet(); + + /** Updates to local tracking branches (if any). */ + private final ArrayList localUpdates = new ArrayList(); + + /** Records to be recorded into FETCH_HEAD. */ + private final ArrayList fetchHeadUpdates = new ArrayList(); + + private final ArrayList packLocks = new ArrayList(); + + private FetchConnection conn; + + FetchProcess(final Transport t, final Collection 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 matched = new HashSet(); + 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 additionalTags = Collections. 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 avail = new HashMap(); + for (final Ref r : conn.getRefs()) + avail.put(r.getObjectId(), r); + + final Collection wants = new ArrayList(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 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 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 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 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 expandAutoFollowTags() throws TransportException { + final Collection additionalTags = new ArrayList(); + final Map 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 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 000000000..df3a1937a --- /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 + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 forMerge; + + FetchResult() { + forMerge = new ArrayList(); + } + + 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 000000000..3793a0abf --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpTransport.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2009, JetBrains s.r.o. + * Copyright (C) 2009, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..f36864847 --- /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 + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * The received pack data and generated index will be saved to temporary + * files within the repository's objects 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 baseById; + + private LongMap 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * This is shorthand for: + * + *

    +	 * setObjectChecker(on ? new ObjectChecker() : null);
    +	 * 
    + * + * @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(); + baseByPos = new LongMap(); + + 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 missing = new ArrayList(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 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. + *

    + * 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. + *

    + * 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 000000000..6381c24dc --- /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 helper for {@link IndexPack}. + * + * @param + * type of the value instance. + */ +final class LongMap { + private static final float LOAD_FACTOR = 0.75f; + + private Node[] 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 n = table[index(key)]; n != null; n = n.next) { + if (n.key == key) + return n.value; + } + return null; + } + + V remove(final long key) { + Node n = table[index(key)]; + Node 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 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(key, value)); + return null; + } + + private void insert(final Node n) { + final int idx = index(n.key); + n.next = table[idx]; + table[idx] = n; + } + + private void grow() { + final Node[] 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 e = oldTable[i]; + while (e != null) { + final Node 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 Node[] createArray(final int sz) { + return new Node[sz]; + } + + private static class Node { + final long key; + + V value; + + Node 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 000000000..e7a307f80 --- /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. + *

    + * 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. + *

    + * 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 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 cache = refresh(); + Host h = cache.get(hostName); + if (h == null) + h = new Host(); + if (h.patternsApplied) + return h; + + for (final Map.Entry 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 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 parse(final InputStream in) throws IOException { + final Map m = new LinkedHashMap(); + final BufferedReader br = new BufferedReader(new InputStreamReader(in)); + final List current = new ArrayList(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() { + public String run() { + return System.getProperty("user.name"); + } + }); + } + + /** + * Configuration of one "Host" block in the configuration file. + *

    + * 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. + *

    + * 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 000000000..c7371d60f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2007-2009, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 advertisedRefs = Collections.emptyMap(); + + URIish uri; + + final SortedMap updates = new TreeMap(); + + /** + * Get the URI this result came from. + *

    + * 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. + *

    + * 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 getAdvertisedRefs() { + return Collections.unmodifiableCollection(advertisedRefs.values()); + } + + /** + * Get a single advertised ref by name. + *

    + * The name supplied should be valid ref name. To get a peeled value for a + * ref (aka refs/tags/v1.0^{}) use the base name (without + * the ^{} 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 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 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 000000000..736d32965 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackTransport.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2009, JetBrains s.r.o. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 000000000..5071cc799 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2008-2009, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 000000000..29fe831ae --- /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 + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..e7a7198d7 --- /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 + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..1e662751b --- /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. + *

    + * 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()}. + *

    + * 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. + *

    + * 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 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 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 000000000..9a743a515 --- /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. + *

    + * 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: + *

      + *
    • receive.denyDenyDeletes
    • + *
    • receive.denyNonFastForwards
    • + *
    + * 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()}. + *

    + * 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)}. + *

    + * 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}. + *

    + * Hooks should run quickly, as they block the caller thread and the client + * process from completing. + *

    + * 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 commands) { + // Do nothing. + } + }; + + /** + * Invoked just before commands are executed. + *

    + * 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 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 000000000..14e6a1e80 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * A push connection typically connects to the git-receive-pack + * 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. + *

    + * 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. + *

    + * 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. + *

    + *

    + * Only one call per connection is allowed. Subsequent calls will result in + * {@link TransportException}. + *

    + *

    + * 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. + *

    + * + * @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 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 000000000..17e1dfc77 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 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 toPush) throws TransportException { + this.walker = new RevWalk(transport.local); + this.transport = transport; + this.toPush = new HashMap(); + 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. + *

    + * 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 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 prepareRemoteUpdates() + throws TransportException { + final Map result = new HashMap(); + 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 000000000..41aa73cc3 --- /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 + * Copyright (C) 2009, Robin Rosenberg + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 remoteUpdates = Collections.emptyMap(); + + /** + * Get status of remote refs updates. Together with + * {@link #getAdvertisedRefs()} it provides full description/status of each + * ref update. + *

    + * Returned collection is not sorted in any order. + *

    + * + * @return collection of remote refs updates + */ + public Collection 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 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 000000000..60ebeabd9 --- /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}. + *

    + * 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. + *

    + * 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. + *

    + * 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 HEAD and cannot be permitted. */ + REJECTED_CURRENT_BRANCH, + + /** + * One or more objects aren't in the repository. + *

    + * 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 000000000..26b66db40 --- /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 refs; + + /** Capabilities requested by the client. */ + private Set enabledCapablities; + + /** Commands to execute, as received by the client. */ + private List 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 KEY = new SectionParser() { + 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 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 getAllCommands() { + return Collections.unmodifiableList(commands); + } + + /** + * Send an error message to the client, if it supports receiving them. + *

    + * If the client doesn't support receiving messages, the message will be + * discarded, with no other indication to the caller or to the client. + *

    + * {@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. + *

    + * 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(); + commands = new ArrayList(); + + 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(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 filterCommands(final Result want) { + final List r = new ArrayList(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 000000000..dfbd891b0 --- /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 capablities = new LinkedHashSet(); + + 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 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 000000000..1949ef087 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * Specifications are typically one of the following forms: + *

      + *
    • refs/head/master
    • + *
    • refs/head/master:refs/remotes/origin/master
    • + *
    • refs/head/*:refs/remotes/origin/*
    • + *
    • +refs/head/master
    • + *
    • +refs/head/master:refs/remotes/origin/master
    • + *
    • +refs/head/*:refs/remotes/origin/*
    • + *
    • :refs/head/master
    • + *
    + * + * @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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * If null during a fetch no tracking branch should be updated and the + * ObjectId should be stored transiently in order to prepare a merge. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 000000000..f05b8c6a3 --- /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 + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 getAllRemoteConfigs(final Config rc) + throws URISyntaxException { + final List names = new ArrayList(rc + .getSubsections(SECTION)); + Collections.sort(names); + + final List result = new ArrayList(names + .size()); + for (final String name : names) + result.add(new RemoteConfig(rc, name)); + return result; + } + + private String name; + + private List uris; + + private List pushURIs; + + private List fetch; + + private List 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. + *

    + * 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(vlst.length); + for (final String s : vlst) + uris.add(new URIish(s)); + + vlst = rc.getStringList(SECTION, name, KEY_PUSHURL); + pushURIs = new ArrayList(vlst.length); + for (final String s : vlst) + pushURIs.add(new URIish(s)); + + vlst = rc.getStringList(SECTION, name, KEY_FETCH); + fetch = new ArrayList(vlst.length); + for (final String s : vlst) + fetch.add(new RefSpec(s)); + + vlst = rc.getStringList(SECTION, name, KEY_PUSH); + push = new ArrayList(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 vlst = new ArrayList(); + + 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 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 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 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 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 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 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. + *

    + * 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. + *

    + * 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 000000000..b2aa6335d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * + */ +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 000000000..7a0765030 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * Reading from this input stream obtains data from channel 1, which is + * typically the bulk data stream. + *

    + * 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. + *

    + * 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 000000000..5e50fd89b --- /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 + *

    + * 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 000000000..89d338c89 --- /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 000000000..c30d32d9f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigSessionFactory.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2009, Google, Inc. + * Copyright (C) 2009, JetBrains s.r.o. + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 + * $HOME/.ssh. + *

    + * 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. + *

    + * 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 byIdentityFile = new HashMap(); + + 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 000000000..76bf6c1dc --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * A factory is always available. By default the factory will read from the + * user's $HOME/.ssh 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. + *

    + * A reasonable UserInfo that can interact with the end-user (if necessary) + * is installed on the returned session by this method. + *

    + * The caller must connect the session by invoking connect() + * 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 + * System.getProperty("user.name"). + * @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 000000000..5c6b498ca --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2009, Google Inc. + * Copyright (C) 2009, JetBrains s.r.o. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008-2009, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..09cd56a72 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TagOpt.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2008, Mike Ralphson + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 000000000..a6e539089 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TcpTransport.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2009, JetBrains s.r.o. + * Copyright (C) 2009, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..2655f39f0 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 000000000..e63afaf08 --- /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 + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 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 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 transports = new ArrayList(1); + transports.add(open(local, new URIish(remote))); + return transports; + } + return openAll(local, cfg, op); + } + + /** + * Open a new transport instance to connect two repositories. + *

    + * 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 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. + *

    + * 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 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 openAll(final Repository local, + final RemoteConfig cfg, final Operation op) + throws NotSupportedException { + final List uris = getURIs(cfg, op); + final List transports = new ArrayList(uris.size()); + for (final URIish uri : uris) { + final Transport tn = open(local, uri); + tn.applyConfig(cfg); + transports.add(tn); + } + return transports; + } + + private static List getURIs(final RemoteConfig cfg, + final Operation op) { + switch (op) { + case FETCH: + return cfg.getURIs(); + case PUSH: { + List 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 findRemoteRefUpdatesFor( + final Repository db, final Collection specs, + Collection fetchSpecs) throws IOException { + if (fetchSpecs == null) + fetchSpecs = Collections.emptyList(); + final List result = new LinkedList(); + final Collection 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 expandPushWildcardsFor( + final Repository db, final Collection specs) { + final Map localRefs = db.getAllRefs(); + final Collection procRefs = new HashSet(); + + 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 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 fetch = Collections.emptyList(); + + /** + * How {@link #fetch(ProgressMonitor, Collection)} should handle tags. + *

    + * 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 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. + *

    + * 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. + *

    + * 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). + *

    + * 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. + *

    + * 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 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 tmp = new ArrayList(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. + *

    + * 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. + *

    + * 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. + *

    + * 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 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. + *

    + * 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 findRemoteRefUpdatesFor( + final Collection 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. + *

    + * 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 000000000..6a1a17f60 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 objects/info/packs + * or info/refs in the remote repository. + *

    + * 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. + *

    + * The prefix does not start with "/". + *

    + * The prefix does not end with "/". The trailing slash is stripped during + * the constructor if a trailing slash was supplied in the URIish. + *

    + * All files within the remote repository start with + * keyPrefix + "/". + */ + 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 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 getPackNames() throws IOException { + final HashSet have = new HashSet(); + have.addAll(s3.list(bucket, resolveKey("pack"))); + + final Collection packs = new ArrayList(); + 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 readAdvertisedRefs() throws TransportException { + final TreeMap avail = new TreeMap(); + readPackedRefs(avail); + readLooseRefs(avail); + readRef(avail, Constants.HEAD); + return avail; + } + + private void readLooseRefs(final TreeMap 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 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 000000000..05be0bbdf --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundle.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2008, Google Inc. + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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). + *

    + * 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 000000000..17e3bdd22 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2008, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..e5188bb23 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleStream.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2008, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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). + *

    + * 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 000000000..a127ff50a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * This transport supports the git:// 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 000000000..55636f8dc --- /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 + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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 000000000..65686b9d4 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 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 getPackNames() throws IOException { + final Collection packs = new ArrayList(); + 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 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 readAdvertisedImpl(final BufferedReader br) + throws IOException, PackProtocolException { + final TreeMap avail = new TreeMap(); + 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 000000000..8bb22275b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2007, Dave Watson + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008, Marek Zawirski + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * This transport is suitable for use on the local system, where the caller has + * direct read or write access to the "remote" repository. + *

    + * 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. + *

    + * However, during {@link #openFetch()}, if the Transport has configured + * {@link Transport#getOptionUploadPack()} to be anything other than + * "git-upload-pack" or "git upload-pack", this + * implementation will fork and execute the external process, using an operating + * system pipe to transfer data. + *

    + * Similarly, during {@link #openPush()}, if the Transport has configured + * {@link Transport#getOptionReceivePack()} to be anything other than + * "git-receive-pack" or "git receive-pack", 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 000000000..8243ddabb --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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 + * objects/info/packs or info/refs files on the + * remote repository and access the data directly, much as Git itself would. + *

    + * 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 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 getPackNames() throws IOException { + final List packs = new ArrayList(); + try { + final Collection list = ftp.ls("pack"); + final HashMap files; + final HashMap mtimes; + + files = new HashMap(); + mtimes = new HashMap(); + + 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() { + 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 readAdvertisedRefs() throws TransportException { + final TreeMap avail = new TreeMap(); + readPackedRefs(avail); + readRef(avail, ROOT_DIR + Constants.HEAD, Constants.HEAD); + readLooseRefs(avail, ROOT_DIR + "refs", "refs/"); + return avail; + } + + private void readLooseRefs(final TreeMap avail, + final String dir, final String prefix) + throws TransportException { + final Collection 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 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 000000000..cfdf47c0c --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..7e534a39c --- /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 refs; + + /** Capabilities requested by the client. */ + private final Set options = new HashSet(); + + /** Objects the client wants to obtain. */ + private final List wantAll = new ArrayList(); + + /** Objects the client wants to obtain. */ + private final List wantCommits = new ArrayList(); + + /** Objects on both sides, these don't have to be sent. */ + private final List commonBase = new ArrayList(); + + /** 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 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 000000000..d368fb2cd --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..8660a195d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java @@ -0,0 +1,878 @@ +/* + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 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 workQueue; + + /** Databases we have not yet obtained the list of packs from. */ + private final LinkedList noPacksYet; + + /** Databases we have not yet obtained the alternates from. */ + private final LinkedList noAlternatesYet; + + /** Packs we have discovered, but have not yet fetched locally. */ + private final LinkedList unfetchedPacks; + + /** + * Packs whose indexes we have looked at in {@link #unfetchedPacks}. + *

    + * 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 packsConsidered; + + private final MutableObjectId idBuffer = new MutableObjectId(); + + private final MessageDigest objectDigest = Constants.newMessageDigest(); + + /** + * Errors received while trying to obtain an object. + *

    + * 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> fetchErrors; + + private String lockMessage; + + private final List packLocks; + + WalkFetchConnection(final WalkTransport t, final WalkRemoteObjectDatabase w) { + Transport wt = (Transport)t; + local = wt.local; + objCheck = wt.isCheckFetchedObjects() ? new ObjectChecker() : null; + + remotes = new ArrayList(); + remotes.add(w); + + unfetchedPacks = new LinkedList(); + packsConsidered = new HashSet(); + + noPacksYet = new LinkedList(); + noPacksYet.add(w); + + noAlternatesYet = new LinkedList(); + noAlternatesYet.add(w); + + fetchErrors = new HashMap>(); + packLocks = new ArrayList(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(); + } + + public boolean didFetchTestConnectivity() { + return true; + } + + @Override + protected void doFetch(final ProgressMonitor monitor, + final Collection want, final Set 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 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 want) + throws TransportException { + final HashSet inWorkQueue = new HashSet(); + 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 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 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 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 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 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 swapFetchQueue() { + final Iterator r = workQueue.iterator(); + workQueue = new LinkedList(); + 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 expandOneAlternate( + final AnyObjectId id, final ProgressMonitor pm) { + while (!noAlternatesYet.isEmpty()) { + final WalkRemoteObjectDatabase wrr = noAlternatesYet.removeFirst(); + try { + pm.beginTask("Listing alternates", ProgressMonitor.UNKNOWN); + Collection 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 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 errors = fetchErrors.get(objId); + if (errors == null) { + errors = new ArrayList(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 000000000..56f73c50b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * This is a LinkedHashMap to maintain the original order. + */ + private LinkedHashMap packNames; + + /** Complete listing of refs the remote will have after our push. */ + private Map newRefs; + + /** + * Updates which require altering the packed-refs file to complete. + *

    + * 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 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 refUpdates) + throws TransportException { + markStartedOperation(); + packNames = null; + newRefs = new TreeMap(getRefsMap()); + packedRefUpdates = new ArrayList(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 updates = new ArrayList(); + 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 updates, + final ProgressMonitor monitor) throws TransportException { + String pathPack = null; + String pathIdx = null; + + try { + final PackWriter pw = new PackWriter(local, monitor); + final List need = new ArrayList(); + final List have = new ArrayList(); + 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(); + 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 infoPacks = new ArrayList(); + 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 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 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 000000000..6a557dfd3 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkRemoteObjectDatabase.java @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * Implementations are responsible for resolving path names relative to the + * objects/ 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). + *

    + * Pack names should be the file name in the packs directory, that is + * pack-035760ab452d6eebd123add421f253ce7682355a.pack. 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 getPackNames() throws IOException; + + /** + * Obtain alternate connections to alternate object databases (if any). + *

    + * 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. + *

    + * 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 getAlternates() + throws IOException; + + /** + * Open a single file for reading. + *

    + * 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. + * cb/95df6ab7ae9e57571511ef451cf33767c26dd2 or + * pack/pack-035760ab452d6eebd123add421f253ce7682355a.pack). + * @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 + *

    + * 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. + *

    + * 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. + *

    + * Path may start with ../ to request deletion of a file that + * resides in the repository itself. + *

    + * When possible empty directories must be removed, up to but not including + * the current object database directory itself. + *

    + * 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. + *

    + * Path may start with ../ to request writing of a file that + * resides in the repository itself. + *

    + * The requested path may or may not exist. If the path already exists as a + * file the file should be truncated and completely replaced. + *

    + * 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. + *

    + * 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. + *

    + * Path may start with ../ to request writing of a file that + * resides in the repository itself. + *

    + * The requested path may or may not exist. If the path already exists as a + * file the file should be truncated and completely replaced. + *

    + * 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 + * refs/heads/pu. + * @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 + * refs/heads/pu. + * @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. + *

    + * This method creates any missing parent directories, if necessary. + * + * @param name + * name of the ref within the ref space, for example + * refs/heads/pu. + * @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. + *

    + * 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 + * pack-035760ab452d6eebd123add421f253ce7682355a.pack. + * @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 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. + *

    + * 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. info/packs). + * @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. + *

    + * This method is suitable for reading the standard formats of the + * alternates file, such as found in objects/info/alternates + * or objects/info/http-alternates within a Git repository. + *

    + * 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. info/alternates). + * @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 readAlternates(final String listPath) + throws IOException { + final BufferedReader br = openReader(listPath); + try { + final Collection alts = new ArrayList(); + 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 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 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 + * i. + */ + 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 000000000..8b440041a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkTransport.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2009, JetBrains s.r.o. + * Copyright (C) 2008, Mike Ralphson + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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 000000000..102974f44 --- /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 + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * Implementors must walk a tree in the Git sort order, which has the following + * odd sorting: + *

      + *
    1. A.c
    2. + *
    3. A/c
    4. + *
    5. A0c
    6. + *
    + *

    + * In the second item, A is the name of a subtree and + * c 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 childPath where the child can + * insert its data. The value at + * childPath[childPathOffset-1] 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. + *

    + * 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. + *

    + * 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. + *

    + * The parent reference of the iterator must be this, + * otherwise the caller would not be able to exit out of the subtree + * iterator correctly and return to continue walking this. + * + * @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. + *

    + * The parent reference of the iterator must be this, otherwise + * the caller would not be able to exit out of the subtree iterator + * correctly and return to continue walking this. + * + * @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? + *

    + * An iterator is positioned on the first entry if back(1) + * would be an invalid request as there is no entry before the current one. + *

    + * An empty iterator (one with no entries) will be + * first() && eof(). + * + * @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)? + *

    + * 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. + *

    + * The delta indicates how many moves forward should occur. The most common + * delta is 1 to move to the next entry. + *

    + * Implementations must populate the following members: + *

      + *
    • {@link #mode}
    • + *
    • {@link #path} (from {@link #pathOffset} to {@link #pathLen})
    • + *
    • {@link #pathLen}
    • + *
    + * 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. + *

    + * The delta indicates how many moves backward should occur.The most common + * delta is 1 to move to the prior entry. + *

    + * Implementations must populate the following members: + *

      + *
    • {@link #mode}
    • + *
    • {@link #path} (from {@link #pathOffset} to {@link #pathLen})
    • + *
    • {@link #pathLen}
    • + *
    + * 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. + *

    + * This method behaves like seek(1) 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. + *

    + * 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 000000000..6705ad992 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 000000000..1776b5088 --- /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 + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 childPath where the child can + * insert its data. The value at + * childPath[childPathOffset-1] 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 000000000..3ef050e7c --- /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 + * Copyright (C) 2008, Shawn O. Pearce + * Copyright (C) 2009, Tor Arne Vestbø + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * This iterator uses the standard java.io 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 000000000..b569174bd --- /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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 000000000..245bea8dc --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * This class can perform n-way differences across as many trees as necessary. + *

    + * Each tree added must have the same root as existing trees in the walk. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * Multiple filters may be combined by constructing an arbitrary tree of + * AndTreeFilter or OrTreeFilter instances to + * describe the boolean expression required by the application. Custom + * filter implementations may also be constructed by applications. + *

    + * 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? + *

    + * 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. + *

    + * 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? + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 + * 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 getTree(final int nth, + final Class clazz) { + final AbstractTreeIterator t = trees[nth]; + return t.matches == currentHead ? (T) t : null; + } + + /** + * Obtain the raw {@link FileMode} bits for the current entry. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 + * getObjectId(nthA).equals(getObjectId(nthB)). + * @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. + *

    + * 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. + *

    + * 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. + *

    + * 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 startsWith 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 buf 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. + *

    + * 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 endsWith or some other type of + * string match function. + * + * @param p + * path buffer to test. + * @param pLen + * number of bytes from buf 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? + *

    + * 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. + *

    + * 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 000000000..18ceef8d9 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -0,0 +1,457 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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}. + *

    + * Most applications will want to use the standard implementation of this + * iterator, {@link FileTreeIterator}, as that does all IO through the standard + * java.io 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. + *

    + * 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_CMP = new Comparator() { + 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. + *

    + * Note: Efficient implementation required. + *

    + * 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. + *

    + * Note: Efficient implementation required. + *

    + * 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. + *

    + * Note: Efficient implementation required. + *

    + * 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. + *

    + * 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. + *

    + * Efficient implementations are not required. The caller will usually + * obtain the stream only once per entry, if at all. + *

    + * 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. + *

    + * 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 000000000..2c3983b01 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 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 000000000..33e4415a9 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 a. + */ + 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 000000000..55005446e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 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 000000000..5883d655e --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 000000000..cd11f8123 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * Path strings use '/' to delimit directories on all platforms. + *

    + * 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 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. + *

    + * 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 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 PATH_SORT = new Comparator() { + 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 000000000..3721ec646 --- /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. + *

    + * 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 000000000..5d0fb12f5 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * This is an abstract interface. Applications may implement a subclass, or use + * one of the predefined implementations already available within this package. + *

    + * 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. + * + *

    + * Path filters: + *

      + *
    • Matching pathname: {@link PathFilter}
    • + *
    + * + *

    + * Difference filters: + *

      + *
    • Only select differences: {@link #ANY_DIFF}.
    • + *
    + * + *

    + * Boolean modifiers: + *

      + *
    • AND: {@link AndTreeFilter}
    • + *
    • OR: {@link OrTreeFilter}
    • + *
    • NOT: {@link NotTreeFilter}
    • + *
    + */ +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. + *

    + * 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. + *

    + * 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. + *

    + * 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? + *

    + * 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. + *

    + * 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 000000000..53c7beced --- /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. + * + *

    + * Change Log: + *

    + *
      + *
    • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added + * some convenience methods for reading and writing to and from files.
    • + *
    • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems + * with other encodings (like EBCDIC).
    • + *
    • v2.0.1 - Fixed an error when decoding a single byte, that is, when the + * encoded data was a single byte.
    • + *
    • 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 (ints that you "OR" together).
    • + *
    • v1.5.1 - Fixed bug when decompressing and decoding to a + * byte[] using decode( String s, boolean gzipCompressed ). + * 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).
    • + *
    • 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.
    • + *
    • v1.4 - Added helper methods to read/write files.
    • + *
    • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
    • + *
    • 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.
    • + *
    • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
    • + *
    • v1.3.3 - Fixed I/O streams which were totally messed up.
    • + *
    + * + *

    + * 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 http://iharder.net/base64 + * periodically to check for updates or to contribute improvements. + *

    + * + * @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 threeBytes + * and returns a four-byte array in Base64 notation. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * The array threeBytes needs only be as big as + * numSigBytes. + * Code can reuse a byte array by passing a four-byte array as b4. + * + * @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 source + * and writes the resulting four Base64 bytes to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accommodate srcOffset + 3 for + * the source array or destOffset + 4 for + * the destination array. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * + * @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 destination 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 null. + * 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 null. + *

    + * Valid options:

    +     *   GZIP: gzip-compresses object before encoding it.
    +     *   DONT_BREAK_LINES: don't break lines at 76 characters
    +     *     Note: Technically, this makes your encoding non-compliant.
    +     * 
    + *

    + * Example: encodeObject( myObj, Base64.GZIP ) or + *

    + * Example: encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * + * @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. + *

    + * Valid options:

    +     *   GZIP: gzip-compresses object before encoding it.
    +     *   DONT_BREAK_LINES: don't break lines at 76 characters
    +     *     Note: Technically, this makes your encoding non-compliant.
    +     * 
    + *

    + * Example: encodeBytes( myData, Base64.GZIP ) or + *

    + * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * + * + * @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. + *

    + * Valid options:

    +     *   GZIP: gzip-compresses object before encoding it.
    +     *   DONT_BREAK_LINES: don't break lines at 76 characters
    +     *     Note: Technically, this makes your encoding non-compliant.
    +     * 
    + *

    + * Example: encodeBytes( myData, Base64.GZIP ) or + *

    + * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * + * + * @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 source + * and writes the resulting bytes (up to three of them) + * to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accommodate srcOffset + 4 for + * the source array or destOffset + 3 for + * the destination 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 + // + + // 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 null 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 true if successful, false 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 true if successful, false 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 + * java.io.InputStream, 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 java.io.InputStream 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. + *

    + * Valid options:

    +         *   ENCODE or DECODE: Encode or Decode as data is read.
    +         *   DONT_BREAK_LINES: don't break lines at 76 characters
    +         *     (only meaningful when encoding)
    +         *     Note: Technically, this makes your encoding non-compliant.
    +         * 
    + *

    + * Example: new Base64.InputStream( in, Base64.DECODE ) + * + * + * @param in the java.io.InputStream 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 len 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 + * java.io.OutputStream, 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 java.io.OutputStream 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. + *

    + * Valid options:

    +         *   ENCODE or DECODE: Encode or Decode as data is read.
    +         *   DONT_BREAK_LINES: don't break lines at 76 characters
    +         *     (only meaningful when encoding)
    +         *     Note: Technically, this makes your encoding non-compliant.
    +         * 
    + *

    + * Example: new Base64.OutputStream( out, Base64.ENCODE ) + * + * @param out the java.io.OutputStream 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 len + * 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 000000000..a52a6530e --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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). + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. new File(dir,name) 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. + *

    + * 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. + *

    + * 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. new File(dir,name) 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). + *

    + * This method can be expensive on the first invocation if path name + * translation is required. Subsequent invocations return a cached result. + *

    + * 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() { + 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 000000000..4ce0366fc --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..8a86d2e65 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX_Java6.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2007, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..79bf1e82e --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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() { + 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 000000000..f72708486 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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() { + 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() { + 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 000000000..40134d0e4 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 http_proxy. + *

    + * The popular libcurl library honors the http_proxy + * 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 http_proxy 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. + *

    + * Roughly the same as c.getResponseCode() 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 000000000..510f2a4db --- /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 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 val 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 000000000..cbe321086 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/MutableInteger.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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 000000000..a42871dbc --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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 000000000..7e5bde758 --- /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. + *

    + * Quotes are unconditionally inserted during {@link #quote(String)}. This + * protects shell meta-characters like $ or ~ from + * being recognized as special. + */ + public static final BourneStyle BOURNE = new BourneStyle(); + + /** Bourne style, but permits ~user at the start of the string. */ + public static final BourneUserPathStyle BOURNE_USER_PATH = new BourneUserPathStyle(); + + /** + * Quote an input string by the quoting rules. + *

    + * If the input string does not require any quoting, the same String + * reference is returned to the caller. + *

    + * 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. + *

    + * This method must match quote such that: + * + *

    +	 * a.equals(dequote(quote(a)));
    +	 * 
    + * + * is true for any a. + * + * @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. + *

    + * This method must match quote such that: + * + *

    +	 * a.equals(dequote(Constants.encode(quote(a))));
    +	 * 
    + * + * is true for any a. + *

    + * 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 in to scan. + * @param end + * one position past in in to scan. + * @return the cleaned string. + */ + public abstract String dequote(byte[] in, int offset, int end); + + /** + * Quoting style used by the Bourne shell. + *

    + * Quotes are unconditionally inserted during {@link #quote(String)}. This + * protects shell meta-characters like $ or ~ 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 ~user 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 000000000..c89705cb6 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 000000000..9254eb3d7 --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * Formatting is performed backwards. The method starts at offset + * o-1 and ends at o-1-digits, where + * digits is the number of positions necessary to store the + * base 10 value. + *

    + * The argument and return values from this method make it easy to chain + * writing, for example: + *

    + * + *
    +	 * 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);
    +	 * 
    + * + * @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 o. 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * 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 [ptr, end) to find line starts. + *

    + * The returned list is 1 indexed. Index 0 contains + * {@link Integer#MIN_VALUE} to pad the list out. + *

    + * Using a 1 indexed list means that line numbers can be directly accessed + * from the list, so list.get(1) (aka get line 1) returns + * ptr. + *

    + * The last element (index map.size()-1) always contains + * end. + * + * @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 buf. + * @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. + *

    + * 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. + *

    + * 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. + *

    + * When passing in a value for nameB 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. + *

    + * When passing in a value for nameB 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. + *

    + * 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 ':'. + *

    + * 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 [start,end), + * 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 [start,end), + * 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 [start,end), + * 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 [start,end), + * 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 [start,end), + * 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 [start,end). + */ + 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. + *

    + * 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; + * b.length 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 000000000..ae135afab --- /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 + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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 rcs of the first occurrence of this + * pattern; -1 if this pattern does not appear at any position of + * rcs. + */ + 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 000000000..91f03f095 --- /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. + *

    + * 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. + *

    + * 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. + *

    + * 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 000000000..771e77058 --- /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 + * Copyright (C) 2009, Yann Simon + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + */ +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 000000000..9c6addebd --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * 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. + *

    + * 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. + *

    + * 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 blocks; + + /** + * Maximum number of bytes we will permit storing in memory. + *

    + * 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. + *

    + * 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(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. + *

    + * 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. + *

    + * 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. + *

    + * 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 + * {@link #length()}/1024. + * @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 000000000..91aa1cb6d --- /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. + *

    + * 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: + * + *

    + * private InterruptTimer myTimer = ...;
    + * void foo() {
    + *   try {
    + *     myTimer.begin(timeout);
    + *     // work
    + *   } finally {
    + *     myTimer.end();
    + *   }
    + * }
    + * 
    + *

    + * 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. + *

    + * 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). + *

    + * 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. + *

    + * 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 000000000..19d7933e1 --- /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 000000000..a826086cd --- /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"); + } +} diff --git a/tag_jgit.sh b/tag_jgit.sh new file mode 100644 index 000000000..77103d861 --- /dev/null +++ b/tag_jgit.sh @@ -0,0 +1,79 @@ +#!/bin/sh +# Copyright (C) 2009, Robin Rosenberg +# and other copyright owners as documented in the project's IP log. +# +# This program and the accompanying materials are made available +# under the terms of the Eclipse Distribution License v1.0 which +# accompanies this distribution, is reproduced below, and is +# available at http://www.eclipse.org/org/documents/edl-v10.php +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or +# without modification, are permitted provided that the following +# conditions are met: +# +# - Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# - Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# - Neither the name of the Eclipse Foundation, Inc. nor the +# names of its contributors may be used to endorse or promote +# products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +# Updates MANIFEST.MF files for EGit plugins. + +v=$1 +if [ -z "$v" ] +then + echo >&2 "usage: $0 version" + exit 1 +fi + +MF=$(git ls-files | grep META-INF/MANIFEST.MF) +MV=jgit-maven/jgit/pom.xml +ALL="$MF $MV" + +replace() { + version=$1 + + perl -pi -e 's/^(Bundle-Version:).*/$1 '$version/ $MF + perl -pi -e 's,^ .*, '$2',' $MV +} + +replace $v $v +git commit -s -m "JGit $v" $ALL && +c=$(git rev-parse HEAD) && + +replace $v.qualifier $v-SNAPSHOT && +git commit -s -m "Re-add version qualifier suffix to $v" $ALL && + +echo && +tagcmd="git tag -s -m 'JGit $v' v$v $c" && +if ! eval $tagcmd +then + echo >&2 + echo >&2 "Tag with:" + echo >&2 " $tagcmd" + exit 1 +fi || exit