aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit')
-rw-r--r--org.eclipse.jgit/.classpath2
-rw-r--r--org.eclipse.jgit/.settings/.api_filters205
-rw-r--r--org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs10
-rw-r--r--org.eclipse.jgit/BUILD1
-rw-r--r--org.eclipse.jgit/META-INF/MANIFEST.MF155
-rw-r--r--org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF5
-rw-r--r--org.eclipse.jgit/OSGI-INF/l10n/plugin.properties (renamed from org.eclipse.jgit/plugin.properties)0
-rw-r--r--org.eclipse.jgit/OSGI-INF/org.eclipse.jgit.internal.util.CleanupService.xml4
-rw-r--r--org.eclipse.jgit/build.properties4
-rw-r--r--org.eclipse.jgit/pom.xml9
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties53
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java97
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java52
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java30
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommitMessageProvider.java72
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java21
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java33
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java52
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java220
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java59
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java48
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java32
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java35
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/PackRefsCommand.java87
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java109
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java41
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java31
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java48
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java92
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java57
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java57
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandRegistry.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java145
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameRegionMerger.java158
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/blame/Candidate.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/blame/Region.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/blame/ReverseWalk.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/blame/cache/BlameCache.java46
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/blame/cache/CacheRegion.java125
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffDriver.java116
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java149
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/EditList.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequence.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequenceComparator.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/LowLevelDiffAlgorithm.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java16
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java52
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/RawTextComparator.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/Subsequence.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/SubsequenceComparator.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/Checkout.java246
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java24
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java336
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java53
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/LargeObjectException.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java24
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/RevisionSyntaxException.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedEvent.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedEvent.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/WildCardHead.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java22
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RegularSuperprojectWriter.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java31
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java59
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/hooks/CommitMsgHook.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/hooks/PreCommitHook.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java47
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diff/FilteredRenameDetector.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java16
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineMergeTool.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalDiffTool.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalMergeTool.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalToolUtils.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/FileElement.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeToolConfig.java16
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java20
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedMergeTool.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java30
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddToBitmapFilter.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddToBitmapWithCacheFilter.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddUnseenToBitmapFilter.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/BitmappedReachabilityChecker.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/ChangedPathFilter.java164
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraph.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphBuilder.java30
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphConstants.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphLoader.java55
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphV1.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphWriter.java289
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/GraphChangedPathFilterData.java68
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/GraphCommits.java16
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/GraphObjectIndex.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/AggregatedBlockCacheStats.java123
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedEvent.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BlockBasedFile.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ClockBlockCacheTable.java575
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java603
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java330
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheStats.java197
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTable.java209
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsConfig.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java88
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java69
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java142
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectRepresentation.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectToPack.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsOutputStream.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java24
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java719
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java21
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPacksChangedEvent.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java319
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java145
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java37
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java17
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefRename.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefUpdate.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java20
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableStack.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryBuilder.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryDescription.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsStreamKey.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsText.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackExtBlockCacheTable.java215
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackInputStream.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java84
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackIndexWriter.java (renamed from org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriter.java)6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java28
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileCommitGraph.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java157
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java73
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java130
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java111
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java360
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LargePackedWholeObject.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectRepresentation.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectToPack.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java36
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java62
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java71
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java27
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java74
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java740
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java57
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java94
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java37
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java16
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexWriterV1.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java112
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFileSnapshot.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java177
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java63
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java63
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV1.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV2.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java46
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexHelper.java92
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexLoader.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexV1.java183
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexWriter.java33
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java160
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexComputed.java205
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexFactory.java117
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexV1.java208
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexWriter.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexWriterV1.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java278
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataInput.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SnapshottingRefDirectory.java102
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java165
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java90
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/BlockSource.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStream.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/memory/TernarySearchTree.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndex.java103
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexConstants.java58
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexLoader.java350
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexPrettyPrinter.java156
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexV1.java289
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriter.java428
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/PackIndexMerger.java337
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/CachedPackUriProvider.java26
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndex.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindowEntry.java24
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectToPack.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackBitmapIndexWriter.java33
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackIndexWriter.java40
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java290
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java20
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/EmptyLogCursor.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/LogCursor.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableBatchRefUpdate.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java59
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableDatabase.java32
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableOutputStream.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReflogReader.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java105
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/http/NetscapeCookieFileCache.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstCommand.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstWant.java20
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java49
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/util/CleanupService.java121
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/util/Optionally.java127
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/util/ShutdownHook.java152
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbrevConfig.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java20
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java132
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java64
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchTrackingStatus.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java317
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java182
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java156
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java226
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java110
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/FileModeCache.java309
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/GitmoduleEntry.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java83
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java95
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignature.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.java158
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifierFactory.java92
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java141
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java20
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectBuilder.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java132
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java152
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java75
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java142
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java59
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifier.java106
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifierFactory.java37
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifiers.java239
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Signer.java139
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/SignerFactory.java37
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Signers.java101
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredConfig.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeFormatter.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java142
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/ContentMergeStrategy.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java46
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeChunk.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java83
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java72
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java36
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java214
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/notes/DefaultNoteMerger.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/notes/Note.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/patch/HunkHeader.java31
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/patch/PatchApplier.java245
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmapWalker.java16
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevPriorityQueue.java150
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java16
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java66
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FollowFilter.java35
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java159
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/Generator.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/LIFORevQueue.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java32
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ReachabilityChecker.java36
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RenameCallback.java22
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevBlob.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java138
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitCG.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitList.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java27
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTree.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java113
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoNonIntermixSortGenerator.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java86
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java91
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MaxCountRevFilter.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/NotRevFilter.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SubStringRevFilter.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java139
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UserConfigFile.java118
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java35
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheStats.java19
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java189
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java20
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleConflict.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java66
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java17
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/AwsRequestSignerV4.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java20
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java105
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/CapabilitiesV2Request.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ConnectivityChecker.java24
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialItem.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java16
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java24
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java49
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java21
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FtpChannel.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java43
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectInfoRequest.java21
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java19
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java17
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHookChain.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHookChain.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHookChain.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateIdent.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java69
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivedPackStatistics.java19
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java29
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RefLeaseSpec.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigStore.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java62
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleStream.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java44
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportProtocol.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java158
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java38
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UsernamePasswordCredentialsProvider.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java76
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java34
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory2.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java31
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java32
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java100
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java17
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ByteArraySet.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ChangedPathTreeFilter.java135
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java21
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotIgnoredFilter.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java17
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java17
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathSuffixFilter.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/SkipWorkTreeFilter.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java78
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/Equality.java26
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java257
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java90
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java17
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java112
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/GSSManagerFactory.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java88
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java248
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java86
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/Iterators.java57
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java188
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java21
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java39
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java25
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java84
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java228
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java41
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/ByteBufferInputStream.java137
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/CountingOutputStream.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/DisabledOutputStream.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java16
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/LimitedInputStream.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/SilentInputStream.java49
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeOutputStream.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java30
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutInputStream.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutOutputStream.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java17
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1Java.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/UbcCheck.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicSystemClock.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java7
628 files changed, 18994 insertions, 6929 deletions
diff --git a/org.eclipse.jgit/.classpath b/org.eclipse.jgit/.classpath
index 139a059adc..dde8e3fce5 100644
--- a/org.eclipse.jgit/.classpath
+++ b/org.eclipse.jgit/.classpath
@@ -2,7 +2,7 @@
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="resources"/>
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
<attributes>
<attribute name="module" value="true"/>
</attributes>
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index 1fbc01102b..eeed75f64e 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -1,212 +1,73 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<component id="org.eclipse.jgit" version="2">
- <resource path="src/org/eclipse/jgit/errors/PackMismatchException.java" type="org.eclipse.jgit.errors.PackMismatchException">
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.9.1"/>
- <message_argument value="isPermanent()"/>
- </message_arguments>
- </filter>
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.9.1"/>
- <message_argument value="setPermanent(boolean)"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/lib/BatchingProgressMonitor.java" type="org.eclipse.jgit.lib.BatchingProgressMonitor">
- <filter id="336695337">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.lib.BatchingProgressMonitor"/>
- <message_argument value="onEndTask(String, int, Duration)"/>
- </message_arguments>
- </filter>
- <filter id="336695337">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.lib.BatchingProgressMonitor"/>
- <message_argument value="onEndTask(String, int, int, int, Duration)"/>
- </message_arguments>
- </filter>
- <filter id="336695337">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.lib.BatchingProgressMonitor"/>
- <message_argument value="onUpdate(String, int, Duration)"/>
- </message_arguments>
- </filter>
- <filter id="336695337">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.lib.BatchingProgressMonitor"/>
- <message_argument value="onUpdate(String, int, int, int, Duration)"/>
- </message_arguments>
- </filter>
- <filter id="338792546">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.lib.BatchingProgressMonitor"/>
- <message_argument value="onEndTask(String, int)"/>
- </message_arguments>
- </filter>
- <filter id="338792546">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.lib.BatchingProgressMonitor"/>
- <message_argument value="onEndTask(String, int, int, int)"/>
- </message_arguments>
- </filter>
- <filter id="338792546">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.lib.BatchingProgressMonitor"/>
- <message_argument value="onUpdate(String, int)"/>
- </message_arguments>
- </filter>
- <filter id="338792546">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.lib.BatchingProgressMonitor"/>
- <message_argument value="onUpdate(String, int, int, int)"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/lib/BranchConfig.java" type="org.eclipse.jgit.lib.BranchConfig$BranchRebaseMode">
- <filter id="372293724">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode"/>
- <message_argument value="PRESERVE"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.13.2"/>
- <message_argument value="CONFIG_KEY_BITMAP_EXCLUDED_REFS_PREFIXES"/>
- </message_arguments>
- </filter>
- <filter id="1142947843">
+ <resource path="META-INF/MANIFEST.MF">
+ <filter id="923795461">
<message_arguments>
- <message_argument value="5.13.2"/>
- <message_argument value="CONFIG_KEY_PRESERVE_OLD_PACKS"/>
+ <message_argument value="7.2.2"/>
+ <message_argument value="7.1.0"/>
</message_arguments>
</filter>
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.13.2"/>
- <message_argument value="CONFIG_KEY_PRUNE_PRESERVED"/>
- </message_arguments>
- </filter>
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.13.2"/>
- <message_argument value="CONFIG_KEY_SKIPHASH"/>
- </message_arguments>
- </filter>
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="6.1.1"/>
- <message_argument value="CONFIG_KEY_TRUST_PACKED_REFS_STAT"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/lib/CoreConfig.java" type="org.eclipse.jgit.lib.CoreConfig">
- <filter id="336658481">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.lib.CoreConfig"/>
- <message_argument value="DEFAULT_COMMIT_GRAPH_ENABLE"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/lib/CoreConfig.java" type="org.eclipse.jgit.lib.CoreConfig$TrustPackedRefsStat">
- <filter id="1142947843">
+ <filter id="934281281">
<message_arguments>
- <message_argument value="6.1.1"/>
- <message_argument value="TrustPackedRefsStat"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/lib/ProgressMonitor.java" type="org.eclipse.jgit.lib.ProgressMonitor">
- <filter id="403804204">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.lib.ProgressMonitor"/>
- <message_argument value="showDuration(boolean)"/>
+ <message_argument value="org.eclipse.jgit.lib"/>
+ <message_argument value="8.1.0"/>
+ <message_argument value="7.1.0"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/lib/RefDatabase.java" type="org.eclipse.jgit.lib.RefDatabase">
- <filter id="336658481">
+ <filter id="336695337">
<message_arguments>
<message_argument value="org.eclipse.jgit.lib.RefDatabase"/>
- <message_argument value="additionalRefsNames"/>
- </message_arguments>
- </filter>
- </resource>
- <resource path="src/org/eclipse/jgit/lib/Repository.java" type="org.eclipse.jgit.lib.Repository">
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.13.2"/>
<message_argument value="getReflogReader(Ref)"/>
</message_arguments>
</filter>
</resource>
- <resource path="src/org/eclipse/jgit/lib/TextProgressMonitor.java" type="org.eclipse.jgit.lib.TextProgressMonitor">
- <filter id="338792546">
+ <resource path="src/org/eclipse/jgit/lib/TypedConfigGetter.java" type="org.eclipse.jgit.lib.TypedConfigGetter">
+ <filter id="403804204">
<message_arguments>
- <message_argument value="org.eclipse.jgit.lib.TextProgressMonitor"/>
- <message_argument value="onEndTask(String, int)"/>
+ <message_argument value="org.eclipse.jgit.lib.TypedConfigGetter"/>
+ <message_argument value="getBoolean(Config, String, String, String, Boolean)"/>
</message_arguments>
</filter>
- <filter id="338792546">
+ <filter id="403804204">
<message_arguments>
- <message_argument value="org.eclipse.jgit.lib.TextProgressMonitor"/>
- <message_argument value="onEndTask(String, int, int, int)"/>
+ <message_argument value="org.eclipse.jgit.lib.TypedConfigGetter"/>
+ <message_argument value="getInt(Config, String, String, String, Integer)"/>
</message_arguments>
</filter>
- <filter id="338792546">
+ <filter id="403804204">
<message_arguments>
- <message_argument value="org.eclipse.jgit.lib.TextProgressMonitor"/>
- <message_argument value="onUpdate(String, int)"/>
+ <message_argument value="org.eclipse.jgit.lib.TypedConfigGetter"/>
+ <message_argument value="getIntInRange(Config, String, String, String, int, int, Integer)"/>
</message_arguments>
</filter>
- <filter id="338792546">
+ <filter id="403804204">
<message_arguments>
- <message_argument value="org.eclipse.jgit.lib.TextProgressMonitor"/>
- <message_argument value="onUpdate(String, int, int, int)"/>
+ <message_argument value="org.eclipse.jgit.lib.TypedConfigGetter"/>
+ <message_argument value="getLong(Config, String, String, String, Long)"/>
</message_arguments>
</filter>
- </resource>
- <resource path="src/org/eclipse/jgit/revwalk/RevCommit.java" type="org.eclipse.jgit.revwalk.RevCommit">
- <filter id="1193279491">
+ <filter id="403804204">
<message_arguments>
- <message_argument value="6.5.1"/>
- <message_argument value="buffer"/>
+ <message_argument value="org.eclipse.jgit.lib.TypedConfigGetter"/>
+ <message_argument value="getTimeUnit(Config, String, String, String, Long, TimeUnit)"/>
</message_arguments>
</filter>
</resource>
- <resource path="src/org/eclipse/jgit/storage/pack/PackConfig.java" type="org.eclipse.jgit.storage.pack.PackConfig">
- <filter id="336658481">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.storage.pack.PackConfig"/>
- <message_argument value="DEFAULT_BITMAP_EXCLUDED_REFS_PREFIXES"/>
- </message_arguments>
- </filter>
- <filter id="336658481">
- <message_arguments>
- <message_argument value="org.eclipse.jgit.storage.pack.PackConfig"/>
- <message_argument value="DEFAULT_MIN_BYTES_FOR_OBJ_SIZE_INDEX"/>
- </message_arguments>
- </filter>
+ <resource path="src/org/eclipse/jgit/revwalk/RevWalk.java" type="org.eclipse.jgit.revwalk.RevWalk">
<filter id="1142947843">
<message_arguments>
- <message_argument value="5.13.2"/>
- <message_argument value="DEFAULT_BITMAP_EXCLUDED_REFS_PREFIXES"/>
+ <message_argument value="6.10.1"/>
+ <message_argument value="isMergedIntoAnyCommit(RevCommit, Collection&lt;RevCommit&gt;)"/>
</message_arguments>
</filter>
- <filter id="1142947843">
- <message_arguments>
- <message_argument value="5.13.2"/>
- <message_argument value="getBitmapExcludedRefsPrefixes()"/>
- </message_arguments>
- </filter>
- <filter id="1142947843">
+ </resource>
+ <resource path="src/org/eclipse/jgit/util/Iterators.java" type="org.eclipse.jgit.util.Iterators">
+ <filter id="1109393411">
<message_arguments>
- <message_argument value="5.13.2"/>
- <message_argument value="setBitmapExcludedRefsPrefixes(String[])"/>
+ <message_argument value="6.10.2"/>
+ <message_argument value="org.eclipse.jgit.util.Iterators"/>
</message_arguments>
</filter>
</resource>
diff --git a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
index 2abe9529d3..ef3d8ecaba 100644
--- a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
+++ b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs
@@ -7,9 +7,9 @@ org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.N
org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=11
+org.eclipse.jdt.core.compiler.compliance=17
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
@@ -40,7 +40,7 @@ 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.invalidJavadocTagsDeprecatedRef=disabled
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private
org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning
@@ -52,7 +52,7 @@ org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=error
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.missingJavadocTagDescription=all_standard_tags
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
@@ -115,7 +115,7 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=error
org.eclipse.jdt.core.compiler.release=enabled
-org.eclipse.jdt.core.compiler.source=11
+org.eclipse.jdt.core.compiler.source=17
org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false
org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
diff --git a/org.eclipse.jgit/BUILD b/org.eclipse.jgit/BUILD
index e806e7d6d0..1e9b7708a0 100644
--- a/org.eclipse.jgit/BUILD
+++ b/org.eclipse.jgit/BUILD
@@ -20,6 +20,7 @@ java_library(
resources = RESOURCES,
deps = [
":insecure_cipher_factory",
+ "//lib:commons-codec",
"//lib:javaewah",
"//lib:slf4j-api",
],
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index 9cc316158a..79e0cc7715 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -3,12 +3,16 @@ Bundle-ManifestVersion: 2
Bundle-Name: %Bundle-Name
Automatic-Module-Name: org.eclipse.jgit
Bundle-SymbolicName: org.eclipse.jgit
-Bundle-Version: 6.6.0.qualifier
-Bundle-Localization: plugin
+Bundle-Version: 7.4.0.qualifier
+Bundle-Localization: OSGI-INF/l10n/plugin
Bundle-Vendor: %Bundle-Vendor
+Bundle-ActivationPolicy: lazy
+Bundle-RequiredExecutionEnvironment: JavaSE-17
+Bundle-SCM: url=https://github.com/eclipse-jgit/jgit, connection=scm:git:https://eclipse.gerrithub.io/eclipse-jgit/jgit.git, developerConnection=scm:git:https://eclipse.gerrithub.io/a/eclipse-jgit/jgit.git
+Service-Component: OSGI-INF/org.eclipse.jgit.internal.util.CleanupService.xml
Eclipse-ExtensibleAPI: true
-Export-Package: org.eclipse.jgit.annotations;version="6.6.0",
- org.eclipse.jgit.api;version="6.6.0";
+Export-Package: org.eclipse.jgit.annotations;version="7.4.0",
+ org.eclipse.jgit.api;version="7.4.0";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.notes,
org.eclipse.jgit.dircache,
@@ -23,72 +27,77 @@ Export-Package: org.eclipse.jgit.annotations;version="6.6.0",
org.eclipse.jgit.revwalk.filter,
org.eclipse.jgit.blame,
org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="6.6.0";
+ org.eclipse.jgit.api.errors;version="7.4.0";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.errors",
- org.eclipse.jgit.attributes;version="6.6.0";
+ org.eclipse.jgit.attributes;version="7.4.0";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk",
- org.eclipse.jgit.blame;version="6.6.0";
+ org.eclipse.jgit.blame;version="7.4.0";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
- org.eclipse.jgit.treewalk.filter,
- org.eclipse.jgit.diff",
- org.eclipse.jgit.diff;version="6.6.0";
+ org.eclipse.jgit.blame.cache,
+ org.eclipse.jgit.diff,
+ org.eclipse.jgit.treewalk.filter",
+ org.eclipse.jgit.blame.cache;version="7.4.0";
+ uses:="org.eclipse.jgit.lib",
+ org.eclipse.jgit.diff;version="7.4.0";
uses:="org.eclipse.jgit.lib,
- org.eclipse.jgit.attributes,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.patch,
+ org.eclipse.jgit.attributes,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.util",
- org.eclipse.jgit.dircache;version="6.6.0";
+ org.eclipse.jgit.dircache;version="7.4.0";
uses:="org.eclipse.jgit.events,
org.eclipse.jgit.lib,
org.eclipse.jgit.attributes,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.util",
- org.eclipse.jgit.errors;version="6.6.0";
+ org.eclipse.jgit.errors;version="7.4.0";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.dircache,
- org.eclipse.jgit.lib,
- org.eclipse.jgit.internal.storage.pack",
- org.eclipse.jgit.events;version="6.6.0";
+ org.eclipse.jgit.lib",
+ org.eclipse.jgit.events;version="7.4.0";
uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="6.6.0",
- org.eclipse.jgit.gitrepo;version="6.6.0";
+ org.eclipse.jgit.fnmatch;version="7.4.0",
+ org.eclipse.jgit.gitrepo;version="7.4.0";
uses:="org.xml.sax.helpers,
org.eclipse.jgit.api,
+ org.eclipse.jgit.api.errors,
org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.xml.sax",
- org.eclipse.jgit.gitrepo.internal;version="6.6.0";x-internal:=true,
- org.eclipse.jgit.hooks;version="6.6.0";uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.ignore;version="6.6.0",
- org.eclipse.jgit.ignore.internal;version="6.6.0";
+ org.eclipse.jgit.gitrepo.internal;version="7.4.0";x-internal:=true,
+ org.eclipse.jgit.hooks;version="7.4.0";
+ uses:="org.eclipse.jgit.lib,
+ org.eclipse.jgit.util",
+ org.eclipse.jgit.ignore;version="7.4.0",
+ org.eclipse.jgit.ignore.internal;version="7.4.0";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal;version="6.6.0";
+ org.eclipse.jgit.internal;version="7.4.0";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.diff;version="6.6.0";
+ org.eclipse.jgit.internal.diff;version="7.4.0";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.diffmergetool;version="6.6.0";
+ org.eclipse.jgit.internal.diffmergetool;version="7.4.0";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.pgm.test,
org.eclipse.jgit.pgm,
org.eclipse.egit.ui",
- org.eclipse.jgit.internal.fsck;version="6.6.0";
+ org.eclipse.jgit.internal.fsck;version="7.4.0";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.revwalk;version="6.6.0";
+ org.eclipse.jgit.internal.revwalk;version="7.4.0";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.storage.commitgraph;version="6.6.0";
+ org.eclipse.jgit.internal.storage.commitgraph;version="7.4.0";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.storage.dfs;version="6.6.0";
+ org.eclipse.jgit.internal.storage.dfs;version="7.4.0";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.http.server,
org.eclipse.jgit.http.test,
org.eclipse.jgit.lfs.test",
- org.eclipse.jgit.internal.storage.file;version="6.6.0";
+ org.eclipse.jgit.internal.storage.file;version="7.4.0";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.junit,
org.eclipse.jgit.junit.http,
@@ -97,39 +106,43 @@ Export-Package: org.eclipse.jgit.annotations;version="6.6.0",
org.eclipse.jgit.pgm,
org.eclipse.jgit.pgm.test,
org.eclipse.jgit.ssh.apache",
- org.eclipse.jgit.internal.storage.io;version="6.6.0";
+ org.eclipse.jgit.internal.storage.io;version="7.4.0";
x-friends:="org.eclipse.jgit.junit,
org.eclipse.jgit.test,
org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.memory;version="6.6.0";
+ org.eclipse.jgit.internal.storage.memory;version="7.4.0";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.storage.pack;version="6.6.0";
+ org.eclipse.jgit.internal.storage.midx;version="7.4.0";x-internal:=true,
+ org.eclipse.jgit.internal.storage.pack;version="7.4.0";
x-friends:="org.eclipse.jgit.junit,
org.eclipse.jgit.test,
org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.reftable;version="6.6.0";
+ org.eclipse.jgit.internal.storage.reftable;version="7.4.0";
x-friends:="org.eclipse.jgit.http.test,
org.eclipse.jgit.junit,
org.eclipse.jgit.test,
org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.submodule;version="6.6.0";x-internal:=true,
- org.eclipse.jgit.internal.transport.connectivity;version="6.6.0";
+ org.eclipse.jgit.internal.submodule;version="7.4.0";x-internal:=true,
+ org.eclipse.jgit.internal.transport.connectivity;version="7.4.0";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.http;version="6.6.0";
+ org.eclipse.jgit.internal.transport.http;version="7.4.0";
x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.parser;version="6.6.0";
+ org.eclipse.jgit.internal.transport.parser;version="7.4.0";
x-friends:="org.eclipse.jgit.http.server,
org.eclipse.jgit.test",
- org.eclipse.jgit.internal.transport.ssh;version="6.6.0";
+ org.eclipse.jgit.internal.transport.ssh;version="7.4.0";
x-friends:="org.eclipse.jgit.ssh.apache,
org.eclipse.jgit.ssh.jsch,
org.eclipse.jgit.test",
- org.eclipse.jgit.lib;version="6.6.0";
+ org.eclipse.jgit.internal.util;version="7.4.0";
+ x-friends:="org.eclipse.jgit.junit",
+ org.eclipse.jgit.lib;version="7.4.0";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.util.sha1,
org.eclipse.jgit.dircache,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.internal.storage.file,
+ org.eclipse.jgit.api,
org.eclipse.jgit.attributes,
org.eclipse.jgit.events,
com.googlecode.javaewah,
@@ -138,12 +151,12 @@ Export-Package: org.eclipse.jgit.annotations;version="6.6.0",
org.eclipse.jgit.util,
org.eclipse.jgit.submodule,
org.eclipse.jgit.util.time",
- org.eclipse.jgit.lib.internal;version="6.6.0";
+ org.eclipse.jgit.lib.internal;version="7.4.0";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.pgm,
org.eclipse.egit.ui",
- org.eclipse.jgit.logging;version="6.6.0",
- org.eclipse.jgit.merge;version="6.6.0";
+ org.eclipse.jgit.logging;version="7.4.0",
+ org.eclipse.jgit.merge;version="7.4.0";
uses:="org.eclipse.jgit.dircache,
org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
@@ -152,67 +165,69 @@ Export-Package: org.eclipse.jgit.annotations;version="6.6.0",
org.eclipse.jgit.util,
org.eclipse.jgit.api,
org.eclipse.jgit.attributes",
- org.eclipse.jgit.nls;version="6.6.0",
- org.eclipse.jgit.notes;version="6.6.0";
+ org.eclipse.jgit.nls;version="7.4.0",
+ org.eclipse.jgit.notes;version="7.4.0";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="6.6.0";
+ org.eclipse.jgit.patch;version="7.4.0";
uses:="org.eclipse.jgit.lib,
+ org.eclipse.jgit.revwalk,
org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="6.6.0";
- uses:="org.eclipse.jgit.lib,
- org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="6.6.0";
+ org.eclipse.jgit.revplot;version="7.4.0";
+ uses:="org.eclipse.jgit.revwalk,
+ org.eclipse.jgit.lib",
+ org.eclipse.jgit.revwalk;version="7.4.0";
uses:="org.eclipse.jgit.lib,
+ org.eclipse.jgit.revwalk.filter,
org.eclipse.jgit.diff,
org.eclipse.jgit.treewalk.filter,
- org.eclipse.jgit.revwalk.filter,
- org.eclipse.jgit.treewalk",
- org.eclipse.jgit.revwalk.filter;version="6.6.0";
+ org.eclipse.jgit.treewalk,
+ org.eclipse.jgit.internal.storage.commitgraph",
+ org.eclipse.jgit.revwalk.filter;version="7.4.0";
uses:="org.eclipse.jgit.revwalk,
org.eclipse.jgit.lib,
org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="6.6.0";
+ org.eclipse.jgit.storage.file;version="7.4.0";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="6.6.0";
+ org.eclipse.jgit.storage.pack;version="7.4.0";
uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="6.6.0";
+ org.eclipse.jgit.submodule;version="7.4.0";
uses:="org.eclipse.jgit.lib,
- org.eclipse.jgit.diff,
org.eclipse.jgit.treewalk.filter,
+ org.eclipse.jgit.diff,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.util",
- org.eclipse.jgit.transport;version="6.6.0";
+ org.eclipse.jgit.transport;version="7.4.0";
uses:="javax.crypto,
+ org.eclipse.jgit.hooks,
org.eclipse.jgit.util.io,
org.eclipse.jgit.lib,
- org.eclipse.jgit.revwalk,
org.eclipse.jgit.transport.http,
- org.eclipse.jgit.internal.storage.file,
+ org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.util,
org.eclipse.jgit.internal.storage.pack,
org.eclipse.jgit.transport.resolver,
org.eclipse.jgit.storage.pack,
org.eclipse.jgit.errors",
- org.eclipse.jgit.transport.http;version="6.6.0";
+ org.eclipse.jgit.transport.http;version="7.4.0";
uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="6.6.0";
+ org.eclipse.jgit.transport.resolver;version="7.4.0";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.lib",
- org.eclipse.jgit.treewalk;version="6.6.0";
+ org.eclipse.jgit.treewalk;version="7.4.0";
uses:="org.eclipse.jgit.dircache,
org.eclipse.jgit.lib,
org.eclipse.jgit.attributes,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.util",
- org.eclipse.jgit.treewalk.filter;version="6.6.0";
+ org.eclipse.jgit.treewalk.filter;version="7.4.0";
uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="6.6.0";
+ org.eclipse.jgit.util;version="7.4.0";
uses:="org.eclipse.jgit.transport,
org.eclipse.jgit.hooks,
org.eclipse.jgit.revwalk,
@@ -225,17 +240,17 @@ Export-Package: org.eclipse.jgit.annotations;version="6.6.0",
org.eclipse.jgit.treewalk,
javax.net.ssl,
org.eclipse.jgit.util.time",
- org.eclipse.jgit.util.io;version="6.6.0";
+ org.eclipse.jgit.util.io;version="7.4.0";
uses:="org.eclipse.jgit.attributes,
org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util.sha1;version="6.6.0",
- org.eclipse.jgit.util.time;version="6.6.0"
-Bundle-RequiredExecutionEnvironment: JavaSE-11
+ org.eclipse.jgit.util.sha1;version="7.4.0",
+ org.eclipse.jgit.util.time;version="7.4.0"
Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)",
javax.crypto,
javax.management,
javax.net.ssl,
- org.slf4j;version="[1.7.0,2.0.0)",
+ org.apache.commons.codec.digest;version="[1.15.0,2.0.0)",
+ org.slf4j;version="[1.7.0,3.0.0)",
org.xml.sax,
org.xml.sax.helpers
diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
index 407d45c748..780059761d 100644
--- a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,6 @@ Bundle-ManifestVersion: 2
Bundle-Name: org.eclipse.jgit - Sources
Bundle-SymbolicName: org.eclipse.jgit.source
Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 6.6.0.qualifier
-Eclipse-SourceBundle: org.eclipse.jgit;version="6.6.0.qualifier";roots="."
+Bundle-Version: 7.4.0.qualifier
+Bundle-SCM: url=https://github.com/eclipse-jgit/jgit, connection=scm:git:https://eclipse.gerrithub.io/eclipse-jgit/jgit.git, developerConnection=scm:git:https://eclipse.gerrithub.io/a/eclipse-jgit/jgit.git
+Eclipse-SourceBundle: org.eclipse.jgit;version="7.4.0.qualifier";roots="."
diff --git a/org.eclipse.jgit/plugin.properties b/org.eclipse.jgit/OSGI-INF/l10n/plugin.properties
index 3e132b01ca..3e132b01ca 100644
--- a/org.eclipse.jgit/plugin.properties
+++ b/org.eclipse.jgit/OSGI-INF/l10n/plugin.properties
diff --git a/org.eclipse.jgit/OSGI-INF/org.eclipse.jgit.internal.util.CleanupService.xml b/org.eclipse.jgit/OSGI-INF/org.eclipse.jgit.internal.util.CleanupService.xml
new file mode 100644
index 0000000000..8d97374c66
--- /dev/null
+++ b/org.eclipse.jgit/OSGI-INF/org.eclipse.jgit.internal.util.CleanupService.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="shutDown" name="org.eclipse.jgit.internal.util.CleanupService">
+ <implementation class="org.eclipse.jgit.internal.util.CleanupService"/>
+</scr:component> \ No newline at end of file
diff --git a/org.eclipse.jgit/build.properties b/org.eclipse.jgit/build.properties
index 0b8f825bf0..e29b88faa0 100644
--- a/org.eclipse.jgit/build.properties
+++ b/org.eclipse.jgit/build.properties
@@ -2,6 +2,6 @@ source.. = src/,\
resources/
output.. = bin/
bin.includes = META-INF/,\
+ OSGI-INF/,\
.,\
- about.html,\
- plugin.properties \ No newline at end of file
+ about.html
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index 100a2ff0ca..ec3f314bef 100644
--- a/org.eclipse.jgit/pom.xml
+++ b/org.eclipse.jgit/pom.xml
@@ -20,7 +20,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>6.6.0-SNAPSHOT</version>
+ <version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit</artifactId>
@@ -46,6 +46,11 @@
<artifactId>slf4j-api</artifactId>
</dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ </dependency>
+
</dependencies>
<build>
@@ -77,7 +82,7 @@
<target>
<copy file="META-INF/SOURCE-MANIFEST.MF" tofile="${source-bundle-manifest}" overwrite="true"/>
<replace file="${source-bundle-manifest}">
- <replacefilter token=".qualifier" value=".${maven.build.timestamp}"/>
+ <replacefilter token=".qualifier" value=".${commit.time.version}"/>
</replace>
</target>
</configuration>
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index bc8144f1c9..e24cba6ac4 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -20,6 +20,8 @@ applyBinaryPatchTypeNotSupported=Couldn't apply binary patch of type {0}
applyTextPatchCannotApplyHunk=Hunk cannot be applied
applyTextPatchSingleClearingHunk=Expected a single hunk for clearing all content
applyBinaryResultOidWrong=Result of binary patch for file {0} has wrong OID
+applyPatchDestInvalid=Destination path in patch is invalid
+applyPatchSourceInvalid==Source path in patch is invalid
applyPatchWithoutSourceOnAlreadyExistingSource=Cannot perform {0} action on an existing file
applyPatchWithCreationOverAlreadyExistingDestination=Cannot perform {0} action which overrides an existing file
applyPatchWithSourceOnNonExistentSource=Cannot perform {0} action on a non-existent file
@@ -64,6 +66,7 @@ binaryHunkLineTooShort=Binary hunk, line {0}: input ended prematurely
binaryHunkMissingNewline=Binary hunk, line {0}: input line not terminated by newline
bitmapMissingObject=Bitmap at {0} is missing {1}.
bitmapsMustBePrepared=Bitmaps must be prepared before they may be written.
+bitmapUseNoopNoListener=Use NOOP instance for no listener
blameNotCommittedYet=Not Committed Yet
blockLimitNotMultipleOfBlockSize=blockLimit {0} must be a multiple of blockSize {1}
blockLimitNotPositive=blockLimit must be positive: {0}
@@ -73,6 +76,7 @@ branchNameInvalid=Branch name {0} is not allowed
buildingBitmaps=Building bitmaps
cachedPacksPreventsIndexCreation=Using cached packs prevents index creation
cachedPacksPreventsListingObjects=Using cached packs prevents listing objects
+cacheRegionAllOrNoneNull=expected all null or none: {0}, {1}
cannotAccessLastModifiedForSafeDeletion=Unable to access lastModifiedTime of file {0}, skip deletion since we cannot safely avoid race condition
cannotBeCombined=Cannot be combined.
cannotBeRecursiveWhenTreesAreIncluded=TreeWalk shouldn't be recursive when tree objects are included.
@@ -156,6 +160,7 @@ commitGraphChunkNeeded=commit-graph 0x{0} chunk has not been loaded
commitGraphChunkRepeated=commit-graph chunk id 0x{0} appears multiple times
commitGraphChunkUnknown=unknown commit-graph chunk: 0x{0}
commitGraphFileIsTooLargeForJgit=commit-graph file is too large for jgit
+commitGraphUnexpectedSize=Commit-graph: expected {0} bytes but out has {1} bytes
commitGraphWritingCancelled=commit-graph writing was canceled
commitMessageNotSpecified=commit message not specified
commitOnRepoWithoutHEADCurrentlyNotSupported=Commit on repo without HEAD currently not supported
@@ -163,6 +168,7 @@ commitAmendOnInitialNotPossible=Amending is not possible on initial commit.
commitsHaveAlreadyBeenMarkedAsStart=Commits have already been marked as walk starts.
compressingObjects=Compressing objects
computingCommitGeneration=Computing commit-graph generation numbers
+computingPathBloomFilters=Computing commit-graph path bloom filters
configSubsectionContainsNewline=config subsection name contains newline
configSubsectionContainsNullByte=config subsection name contains byte 0x00
configValueContainsNullByte=config value contains byte 0x00
@@ -223,6 +229,7 @@ corruptObjectTruncatedInMode=truncated in mode
corruptObjectTruncatedInName=truncated in name
corruptObjectTruncatedInObjectId=truncated in object id
corruptObjectZeroId=entry points to null SHA-1
+corruptReverseIndexChecksumIncorrect=Reverse index checksum incorrect: written as {0} but digest was {1}
corruptUseCnt=close() called when useCnt is already zero for {0}
couldNotGetAdvertisedRef=Remote {0} did not advertise Ref for branch {1}. This Ref may not exist in the remote or may be hidden by permission settings.
couldNotGetRepoStatistics=Could not get repository statistics
@@ -255,9 +262,11 @@ deleteFileFailed=Could not delete file {0}
deletedOrphanInPackDir=Deleted orphaned file {}
deleteRequiresZeroNewId=Delete requires new ID to be zero
deleteTagUnexpectedResult=Delete tag returned unexpected result {0}
+deletingBranches=Deleting branches...
deletingNotSupported=Deleting {0} not supported.
depthMustBeAt1=Depth must be >= 1
depthWithUnshallow=Depth and unshallow can\'t be used together
+deprecatedTrustFolderStat=Option core.trustFolderStat is deprecated, replace it by core.trustStat.
destinationIsNotAWildcard=Destination is not a wildcard.
detachedHeadDetected=HEAD is detached
diffToolNotGivenError=No diff tool provided and no defaults configured.
@@ -276,6 +285,9 @@ DIRCUnrecognizedExtendedFlags=Unrecognized extended flags: {0}
downloadCancelled=Download cancelled
downloadCancelledDuringIndexing=Download cancelled during indexing
duplicateAdvertisementsOf=duplicate advertisements of {0}
+duplicateCacheTablesGiven=Duplicate cache tables given
+duplicatePackExtensionsForCacheTables=Duplicate pack extension {0} in cache tables
+duplicatePackExtensionsSet=Attempting to configure duplicate pack extensions: {0}.{1}.{2} contains {3}
duplicateRef=Duplicate ref: {0}
duplicateRefAttribute=Duplicate ref attribute: {0}
duplicateRemoteRefUpdateIsIllegal=Duplicate remote ref update is illegal. Affected remote name: {0}
@@ -385,6 +397,7 @@ initFailedGitDirIsNoDirectory=Cannot set git-dir to ''{0}'' which is not a direc
initFailedNonBareRepoSameDirs=When initializing a non-bare repo with directory {0} and separate git-dir {1} specified both folders should not point to the same location
inMemoryBufferLimitExceeded=In-memory buffer limit exceeded
inputDidntMatchLength=Input did not match supplied length. {0} bytes are missing.
+inputStreamClosed=InputStream was closed
inputStreamMustSupportMark=InputStream must support mark()
integerValueNotInRange=Integer value {0}.{1} = {2} not in range {3}..{4}
integerValueNotInRangeSubSection=Integer value {0}.{1}.{2} = {3} not in range {4}..{5}
@@ -451,6 +464,7 @@ invalidTimestamp=Invalid timestamp in {0}
invalidTimeUnitValue2=Invalid time unit value: {0}.{1}={2}
invalidTimeUnitValue3=Invalid time unit value: {0}.{1}.{2}={3}
invalidTreeZeroLengthName=Cannot append a tree entry with zero-length name
+invalidTrustStat=core.trustStat must not be set to TrustStat.INHERIT, falling back to TrustStat.ALWAYS.
invalidURL=Invalid URL {0}
invalidWildcards=Invalid wildcards {0}
invalidRefSpec=Invalid refspec {0}
@@ -482,6 +496,7 @@ logInvalidDefaultCharset=System property "native.encoding" specifies unknown cha
logLargerFiletimeDiff={}: inconsistent duration from file timestamps on {}, {}: diff = {} > {} (last good value). Aborting measurement.
logSmallerFiletime={}: got smaller file timestamp on {}, {}: {} < {}. Aborting measurement at resolution {}.
logXDGConfigHomeInvalid=Environment variable XDG_CONFIG_HOME contains an invalid path {}
+logXDGCacheHomeInvalid=Environment variable XDG_CACHE_HOME contains an invalid path {}
looseObjectHandleIsStale=loose-object {0} file handle is stale. retry {1} of {2}
maxCountMustBeNonNegative=max count must be >= 0
mergeConflictOnNonNoteEntries=Merge conflict on non-note entries: base = {0}, ours = {1}, theirs = {2}
@@ -489,11 +504,14 @@ mergeConflictOnNotes=Merge conflict on note {0}. base = {1}, ours = {2}, theirs
mergeStrategyAlreadyExistsAsDefault=Merge strategy "{0}" already exists as a default strategy
mergeStrategyDoesNotSupportHeads=merge strategy {0} does not support {1} heads to be merged into HEAD
mergeUsingStrategyResultedInDescription=Merge of revisions {0} with base {1} using strategy {2} resulted in: {3}. {4}
-mergeRecursiveConflictsWhenMergingCommonAncestors=Multiple common ancestors were found and merging them resulted in a conflict: {0}, {1}
+mergeRecursiveConflictsWhenMergingCommonAncestors=Multiple common ancestors were found and merging them resulted in a conflict: {0}, {1}\nFailing paths: {2}
mergeRecursiveTooManyMergeBasesFor = "More than {0} merge bases for:\n a {1}\n b {2} found:\n count {3}"
mergeToolNotGivenError=No merge tool provided and no defaults configured.
mergeToolNullError=Parameter for merge tool cannot be null.
messageAndTaggerNotAllowedInUnannotatedTags = Unannotated tags cannot have a message or tagger
+midxChunkNeeded=midx 0x{0} chunk has not been loaded
+midxChunkRepeated=midx chunk id 0x{0} appears multiple times
+midxChunkUnknown=unknown midx chunk: 0x{0}
minutesAgo={0} minutes ago
mismatchOffset=mismatch offset for object {0}
mismatchCRC=mismatch CRC for object {0}
@@ -514,6 +532,10 @@ mkDirsFailed=Creating directories for {0} failed
month=month
months=months
monthsAgo={0} months ago
+multiPackIndexFileIsTooLargeForJgit=Multipack index file is too large for jgit
+multiPackIndexPackCountMismatch=Multipack index: header mentions {0} packs but packfile names chunk has {1}
+multiPackIndexUnexpectedSize=MultiPack index: expected {0} bytes but out has {1} bytes
+multiPackIndexWritingCancelled=Multipack index writing was canceled
multipleMergeBasesFor=Multiple merge bases for:\n {0}\n {1} found:\n {2}\n {3}
nameMustNotBeNullOrEmpty=Ref name must not be null or empty.
need2Arguments=Need 2 arguments
@@ -529,6 +551,8 @@ noMergeBase=No merge base could be determined. Reason={0}. {1}
noMergeHeadSpecified=No merge head specified
nonBareLinkFilesNotSupported=Link files are not supported with nonbare repos
nonCommitToHeads=Cannot point a branch to a non-commit object
+noPackExtConfigurationGiven=No PackExt configuration given
+noPackExtGivenForConfiguration=No PackExt given for configuration
noPathAttributesFound=No Attributes found for {0}.
noSuchRef=no such ref
noSuchRefKnown=no such ref: {0}
@@ -539,6 +563,7 @@ notACommitGraph=not a commit-graph
notADIRCFile=Not a DIRC file.
notAGitDirectory=not a git directory
notAPACKFile=Not a PACK file.
+notAMIDX=not a multi-pack-index
notARef=Not a ref: {0}: {1}
notASCIIString=Not ASCII string: {0}
notAuthorized=not authorized
@@ -549,6 +574,7 @@ nothingToPush=Nothing to push.
notMergedExceptionMessage=Branch was not deleted as it has not been merged yet; use the force option to delete it anyway
notShallowedUnshallow=The server sent a unshallow for a commit that wasn''t marked as shallow: {0}
noXMLParserAvailable=No XML parser available.
+nullRevCommit=RevCommit is null
numberDoesntFit=Number doesn't fit in a single byte
objectAtHasBadZlibStream=Object at {0} in {1} has bad zlib stream
objectIsCorrupt=Object {0} is corrupt: {1}
@@ -560,13 +586,12 @@ obtainingCommitsForCherryPick=Obtaining commits that need to be cherry-picked
oldIdMustNotBeNull=Expected old ID must not be null
onlyOneFetchSupported=Only one fetch supported
onlyOneOperationCallPerConnectionIsSupported=Only one operation call per connection is supported.
-onlyOpenPgpSupportedForSigning=OpenPGP is the only supported signing option with JGit at this time (gpg.format must be set to openpgp).
openFilesMustBeAtLeast1=Open files must be >= 1
openingConnection=Opening connection
operationCanceled=Operation {0} was canceled
outputHasAlreadyBeenStarted=Output has already been started.
overflowedReftableBlock=Overflowed reftable block
-packChecksumMismatch=Pack checksum mismatch detected for pack file {0}: .pack has {1} whilst .idx has {2}
+packChecksumMismatch=Pack checksum mismatch detected for pack file {0}: {1} has {2} whilst {3} has {4}
packCorruptedWhileWritingToFilesystem=Pack corrupted while writing to filesystem
packedRefsHandleIsStale=packed-refs handle is stale, {0}. retry
packetSizeMustBeAtLeast=packet size {0} must be >= {1}
@@ -582,6 +607,8 @@ packInaccessible=Failed to access pack file {0}, caught {1} consecutive errors w
packingCancelledDuringObjectsWriting=Packing cancelled during objects writing
packObjectCountMismatch=Pack object count mismatch: pack {0} index {1}: {2}
packRefs=Pack refs
+packRefsFailed=Packing refs failed
+packRefsSuccessful=Packed refs successfully
packSizeNotSetYet=Pack size not yet set since it has not yet been received
packTooLargeForIndexVersion1=Pack too large for index version 1
packWasDeleted=Pack file {0} was deleted, removing it from pack list
@@ -598,6 +625,7 @@ peerDidNotSupplyACompleteObjectGraph=peer did not supply a complete object graph
personIdentEmailNonNull=E-mail address of PersonIdent must not be null.
personIdentNameNonNull=Name of PersonIdent must not be null.
postCommitHookFailed=Execution of post-commit hook failed: {0}.
+precedenceTrustConfig=Both core.trustFolderStat and core.trustStat are set, ignoring trustFolderStat since trustStat takes precedence. Remove core.trustFolderStat from your configuration.
prefixRemote=remote:
problemWithResolvingPushRefSpecsLocally=Problem with resolving push ref specs locally: {0}
progressMonUploading=Uploading {0}
@@ -625,8 +653,6 @@ readFileStoreAttributesFailed=Reading FileStore attributes from user config fail
readerIsRequired=Reader is required
readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0}
readLastModifiedFailed=Reading lastModified of {0} failed
-readPipeIsNotAllowed=FS.readPipe() isn't allowed for command ''{0}''. Working directory: ''{1}''.
-readPipeIsNotAllowedRequiredPermission=FS.readPipe() isn't allowed for command ''{0}''. Working directory: ''{1}''. Required permission: {2}.
readTimedOut=Read timed out after {0} ms
receivePackObjectTooLarge1=Object too large, rejecting the pack. Max object size limit is {0} bytes.
receivePackObjectTooLarge2=Object too large ({0} bytes), rejecting the pack. Max object size limit is {1} bytes.
@@ -689,6 +715,7 @@ searchForReachableBranches=Finding reachable branches
saveFileStoreAttributesFailed=Saving measured FileStore attributes to user config failed
searchForReuse=Finding sources
searchForReuseTimeout=Search for reuse timed out after {0} seconds
+unsupportedObjectIdVersion=Object id version {0} is not supported
searchForSizes=Getting sizes
secondsAgo={0} seconds ago
selectingCommits=Selecting commits
@@ -703,6 +730,11 @@ shortCompressedStreamAt=Short compressed stream at {0}
shortReadOfBlock=Short read of block.
shortReadOfOptionalDIRCExtensionExpectedAnotherBytes=Short read of optional DIRC extension {0}; expected another {1} bytes within the section.
shortSkipOfBlock=Short skip of block.
+shutdownCleanup=Cleanup {} during JVM shutdown
+shutdownCleanupFailed=Cleanup during JVM shutdown failed
+shutdownCleanupListenerFailed=Cleanup of {0} during JVM shutdown failed
+signatureServiceConflict={0} conflict for type {1}. Already registered is {2}; additional factory {3} is ignored.
+signatureTypeUnknown=No signer for {0} signatures. Use another signature type for git config gpg.format, or do not sign.
signatureVerificationError=Signature verification failed
signatureVerificationUnavailable=No signature verifier registered
signedTagMessageNoLf=A non-empty message of a signed tag must end in LF.
@@ -788,6 +820,7 @@ truncatedHunkOldLinesMissing=Truncated hunk, at least {0} old lines is missing
tSizeMustBeGreaterOrEqual1=tSize must be >= 1
unableToCheckConnectivity=Unable to check connectivity.
unableToCreateNewObject=Unable to create new object: {0}
+unableToReadFullArray=Unable to read an array with {0} elements from the stream
unableToReadFullInt=Unable to read a full int from the stream
unableToReadPackfile=Unable to read packfile {0}
unableToRemovePath=Unable to remove path ''{0}''
@@ -814,7 +847,8 @@ unknownObject=unknown object
unknownObjectInIndex=unknown object {0} found in index but not in pack file
unknownObjectType=Unknown object type {0}.
unknownObjectType2=unknown
-unknownPositionEncoding=Unknown position encoding %s
+unknownPackExtension=Unknown pack extension: {0}.{1}.{2}={3}
+unknownPositionEncoding=Unknown position encoding {0}
unknownRefStorageFormat=Unknown ref storage format "{0}"
unknownRepositoryFormat=Unknown repository format
unknownRepositoryFormat2=Unknown repository format "{0}"; expected "0".
@@ -825,6 +859,8 @@ unmergedPath=Unmerged path: {0}
unmergedPaths=Repository contains unmerged paths
unpackException=Exception while parsing pack stream
unreadableCommitGraph=Unreadable commit-graph: {0}
+unreadableMIDX=Unreadable multi-pack-index: {0}
+unreadableObjectSizeIndex=Unreadable object size index. First {0} bytes are ''{1}''
unreadablePackIndex=Unreadable pack index: {0}
unrecognizedPackExtension=Unrecognized pack extension: {0}
unrecognizedRef=Unrecognized ref: {0}
@@ -837,6 +873,8 @@ unsupportedEncryptionAlgorithm=Unsupported encryption algorithm: {0}
unsupportedEncryptionVersion=Unsupported encryption version: {0}
unsupportedGC=Unsupported garbage collector for repository type: {0}
unsupportedMark=Mark not supported
+unsupportedMIDXVersion=Unsupported MIDX version {0}
+unsupportedObjectSizeIndexVersion=Unsupported object size index version {0}
unsupportedOperationNotAddAtEnd=Not add-at-end: {0}
unsupportedPackIndexVersion=Unsupported pack index version {0}
unsupportedPackReverseIndexVersion=Unsupported pack reverse index version {0}
@@ -845,6 +883,7 @@ unsupportedReftableVersion=Unsupported reftable version {0}.
unsupportedRepositoryDescription=Repository description not supported
unsupportedSizesObjSizeIndex=Unsupported sizes in object-size-index
updateRequiresOldIdAndNewId=Update requires both old ID and new ID to be nonzero
+updatingConfig=Updating git config
updatingHeadFailed=Updating HEAD failed
updatingReferences=Updating references
updatingRefFailed=Updating the ref {0} to {1} failed. ReturnCode from RefUpdate.update() was {2}
@@ -873,7 +912,7 @@ writerAlreadyInitialized=Writer already initialized
writeTimedOut=Write timed out after {0} ms
writingNotPermitted=Writing not permitted
writingNotSupported=Writing {0} not supported.
-writingOutCommitGraph=Writing out commit-graph in {0} passes
+writingOutCommitGraph=Writing out commit-graph
writingObjects=Writing objects
wrongDecompressedLength=wrong decompressed length
wrongRepositoryState=Wrong Repository State: {0}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
index cb32324043..b4d1cab513 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
- * Copyright (C) 2010, Stefan Lay <stefan.lay@sap.com> and others
+ * Copyright (C) 2010, 2025 Stefan Lay <stefan.lay@sap.com> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -17,9 +17,10 @@ import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
import java.io.IOException;
import java.io.InputStream;
+import java.text.MessageFormat;
import java.time.Instant;
-import java.util.Collection;
-import java.util.LinkedList;
+import java.util.ArrayList;
+import java.util.List;
import org.eclipse.jgit.api.errors.FilterFailedException;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -55,12 +56,19 @@ import org.eclipse.jgit.treewalk.filter.TreeFilter;
*/
public class AddCommand extends GitCommand<DirCache> {
- private Collection<String> filepatterns;
+ private List<String> filepatterns;
private WorkingTreeIterator workingTreeIterator;
+ // Update only known index entries, don't add new ones. If there's no file
+ // for an index entry, remove it: stage deletions.
private boolean update = false;
+ // If TRUE, also stage deletions, otherwise only update and add index
+ // entries.
+ // If not set explicitly
+ private Boolean all;
+
// This defaults to true because it's what JGit has been doing
// traditionally. The C git default would be false.
private boolean renormalize = true;
@@ -73,7 +81,7 @@ public class AddCommand extends GitCommand<DirCache> {
*/
public AddCommand(Repository repo) {
super(repo);
- filepatterns = new LinkedList<>();
+ filepatterns = new ArrayList<>();
}
/**
@@ -82,6 +90,17 @@ public class AddCommand extends GitCommand<DirCache> {
* A directory name (e.g. <code>dir</code> to add <code>dir/file1</code> and
* <code>dir/file2</code>) can also be given to add all files in the
* directory, recursively. Fileglobs (e.g. *.c) are not yet supported.
+ * </p>
+ * <p>
+ * If a pattern {@code "."} is added, all changes in the git repository's
+ * working tree will be added.
+ * </p>
+ * <p>
+ * File patterns are required unless {@code isUpdate() == true} or
+ * {@link #setAll(boolean)} is called. If so and no file patterns are given,
+ * all changes will be added (i.e., a file pattern of {@code "."} is
+ * implied).
+ * </p>
*
* @param filepattern
* repository-relative path of file/directory to add (with
@@ -113,15 +132,41 @@ public class AddCommand extends GitCommand<DirCache> {
* Executes the {@code Add} command. Each instance of this class should only
* be used for one invocation of the command. Don't call this method twice
* on an instance.
+ * </p>
+ *
+ * @throws JGitInternalException
+ * on errors, but also if {@code isUpdate() == true} _and_
+ * {@link #setAll(boolean)} had been called
+ * @throws NoFilepatternException
+ * if no file patterns are given if {@code isUpdate() == false}
+ * and {@link #setAll(boolean)} was not called
*/
@Override
public DirCache call() throws GitAPIException, NoFilepatternException {
-
- if (filepatterns.isEmpty())
- throw new NoFilepatternException(JGitText.get().atLeastOnePatternIsRequired);
checkCallable();
+
+ if (update && all != null) {
+ throw new JGitInternalException(MessageFormat.format(
+ JGitText.get().illegalCombinationOfArguments,
+ "--update", "--all/--no-all")); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ boolean addAll;
+ if (filepatterns.isEmpty()) {
+ if (update || all != null) {
+ addAll = true;
+ } else {
+ throw new NoFilepatternException(
+ JGitText.get().atLeastOnePatternIsRequired);
+ }
+ } else {
+ addAll = filepatterns.contains("."); //$NON-NLS-1$
+ if (all == null && !update) {
+ all = Boolean.TRUE;
+ }
+ }
+ boolean stageDeletions = update || (all != null && all.booleanValue());
+
DirCache dc = null;
- boolean addAll = filepatterns.contains("."); //$NON-NLS-1$
try (ObjectInserter inserter = repo.newObjectInserter();
NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) {
@@ -181,7 +226,8 @@ public class AddCommand extends GitCommand<DirCache> {
if (f == null) { // working tree file does not exist
if (entry != null
- && (!update || GITLINK == entry.getFileMode())) {
+ && (!stageDeletions
+ || GITLINK == entry.getFileMode())) {
builder.add(entry);
}
continue;
@@ -252,7 +298,8 @@ public class AddCommand extends GitCommand<DirCache> {
}
/**
- * Set whether to only match against already tracked files
+ * Set whether to only match against already tracked files. If
+ * {@code update == true}, re-sets a previous {@link #setAll(boolean)}.
*
* @param update
* If set to true, the command only matches {@code filepattern}
@@ -314,4 +361,32 @@ public class AddCommand extends GitCommand<DirCache> {
public boolean isRenormalize() {
return renormalize;
}
+
+ /**
+ * Defines whether the command will use '--all' mode: update existing index
+ * entries, add new entries, and remove index entries for which there is no
+ * file. (In other words: also stage deletions.)
+ * <p>
+ * The setting is independent of {@link #setUpdate(boolean)}.
+ * </p>
+ *
+ * @param all
+ * whether to enable '--all' mode
+ * @return {@code this}
+ * @since 7.2
+ */
+ public AddCommand setAll(boolean all) {
+ this.all = Boolean.valueOf(all);
+ return this;
+ }
+
+ /**
+ * Tells whether '--all' has been set for this command.
+ *
+ * @return {@code true} if it was set; {@code false} otherwise
+ * @since 7.2
+ */
+ public boolean isAll() {
+ return all != null && all.booleanValue();
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java
index ceb17fbe25..8805ea2353 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java
@@ -51,7 +51,6 @@ public class AddNoteCommand extends GitCommand<Note> {
super(repo);
}
- /** {@inheritDoc} */
@Override
public Note call() throws GitAPIException {
checkCallable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
index e612924771..df0f616256 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
@@ -37,11 +37,12 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
/**
* Constructs the command.
*
- * @param local
+ * @param repo
+ * the repository this command will be used on
*/
- ApplyCommand(Repository local) {
- super(local);
- if (local == null) {
+ ApplyCommand(Repository repo) {
+ super(repo);
+ if (repo == null) {
throw new NullPointerException(JGitText.get().repositoryIsRequired);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java
index fdf8b80cd4..81b3611774 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java
@@ -177,6 +177,8 @@ public class ArchiveCommand extends GitCommand<OutputStream> {
}
/**
+ * Get the problematic format name
+ *
* @return the problematic format name
*/
public String getFormat() {
@@ -389,7 +391,6 @@ public class ArchiveCommand extends GitCommand<OutputStream> {
}
}
- /** {@inheritDoc} */
@Override
public OutputStream call() throws GitAPIException {
checkCallable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
index 7319ff4b2f..32c242f271 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com>
- * Copyright (C) 2011, 2020 Matthias Sohn <matthias.sohn@sap.com> and others
+ * Copyright (C) 2011, 2023 Matthias Sohn <matthias.sohn@sap.com> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -17,7 +17,6 @@ import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -28,6 +27,7 @@ import org.eclipse.jgit.api.errors.InvalidRefNameException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
import org.eclipse.jgit.api.errors.RefNotFoundException;
+import org.eclipse.jgit.dircache.Checkout;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
@@ -55,7 +55,6 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.treewalk.WorkingTreeOptions;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
/**
@@ -164,10 +163,9 @@ public class CheckoutCommand extends GitCommand<Ref> {
*/
protected CheckoutCommand(Repository repo) {
super(repo);
- this.paths = new LinkedList<>();
+ this.paths = new ArrayList<>();
}
- /** {@inheritDoc} */
@Override
public Ref call() throws GitAPIException, RefAlreadyExistsException,
RefNotFoundException, InvalidRefNameException,
@@ -326,6 +324,8 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
/**
+ * Set progress monitor
+ *
* @param monitor
* a progress monitor
* @return this instance
@@ -407,13 +407,14 @@ public class CheckoutCommand extends GitCommand<Ref> {
*
* @return this instance
* @throws java.io.IOException
+ * if an IO error occurred
* @throws org.eclipse.jgit.api.errors.RefNotFoundException
+ * if {@code Ref} couldn't be resolved
*/
protected CheckoutCommand checkoutPaths() throws IOException,
RefNotFoundException {
actuallyModifiedPaths = new HashSet<>();
- WorkingTreeOptions options = repo.getConfig()
- .get(WorkingTreeOptions.KEY);
+ Checkout checkout = new Checkout(repo).setRecursiveDeletion(true);
DirCache dc = repo.lockDirCache();
try (RevWalk revWalk = new RevWalk(repo);
TreeWalk treeWalk = new TreeWalk(repo,
@@ -422,10 +423,10 @@ public class CheckoutCommand extends GitCommand<Ref> {
if (!checkoutAllPaths)
treeWalk.setFilter(PathFilterGroup.createFromStrings(paths));
if (isCheckoutIndex())
- checkoutPathsFromIndex(treeWalk, dc, options);
+ checkoutPathsFromIndex(treeWalk, dc, checkout);
else {
RevCommit commit = revWalk.parseCommit(getStartPointObjectId());
- checkoutPathsFromCommit(treeWalk, dc, commit, options);
+ checkoutPathsFromCommit(treeWalk, dc, commit, checkout);
}
} finally {
try {
@@ -443,7 +444,7 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
private void checkoutPathsFromIndex(TreeWalk treeWalk, DirCache dc,
- WorkingTreeOptions options)
+ Checkout checkout)
throws IOException {
DirCacheIterator dci = new DirCacheIterator(dc);
treeWalk.addTree(dci);
@@ -469,7 +470,7 @@ public class CheckoutCommand extends GitCommand<Ref> {
if (stage > DirCacheEntry.STAGE_0) {
if (checkoutStage != null) {
if (stage == checkoutStage.number) {
- checkoutPath(ent, r, options,
+ checkoutPath(ent, r, checkout, path,
new CheckoutMetadata(eolStreamType,
filterCommand));
actuallyModifiedPaths.add(path);
@@ -480,7 +481,7 @@ public class CheckoutCommand extends GitCommand<Ref> {
throw new JGitInternalException(e.getMessage(), e);
}
} else {
- checkoutPath(ent, r, options,
+ checkoutPath(ent, r, checkout, path,
new CheckoutMetadata(eolStreamType,
filterCommand));
actuallyModifiedPaths.add(path);
@@ -494,7 +495,7 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
private void checkoutPathsFromCommit(TreeWalk treeWalk, DirCache dc,
- RevCommit commit, WorkingTreeOptions options) throws IOException {
+ RevCommit commit, Checkout checkout) throws IOException {
treeWalk.addTree(commit.getTree());
final ObjectReader r = treeWalk.getObjectReader();
DirCacheEditor editor = dc.editor();
@@ -516,7 +517,7 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
ent.setObjectId(blobId);
ent.setFileMode(mode);
- checkoutPath(ent, r, options,
+ checkoutPath(ent, r, checkout, path,
new CheckoutMetadata(eolStreamType, filterCommand));
actuallyModifiedPaths.add(path);
}
@@ -526,10 +527,9 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
- WorkingTreeOptions options, CheckoutMetadata checkoutMetadata) {
+ Checkout checkout, String path, CheckoutMetadata checkoutMetadata) {
try {
- DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
- checkoutMetadata, options);
+ checkout.checkout(entry, checkoutMetadata, reader, path);
} catch (IOException e) {
throw new JGitInternalException(MessageFormat.format(
JGitText.get().checkoutConflictWithFile,
@@ -644,24 +644,6 @@ public class CheckoutCommand extends GitCommand<Ref> {
/**
* Specify to force the ref update in case of a branch switch.
*
- * @param force
- * if <code>true</code> and the branch with the given name
- * already exists, the start-point of an existing branch will be
- * set to a new start-point; if false, the existing branch will
- * not be changed
- * @return this instance
- * @deprecated this method was badly named comparing its semantics to native
- * git's checkout --force option, use
- * {@link #setForceRefUpdate(boolean)} instead
- */
- @Deprecated
- public CheckoutCommand setForce(boolean force) {
- return setForceRefUpdate(force);
- }
-
- /**
- * Specify to force the ref update in case of a branch switch.
- *
* In releases prior to 5.2 this method was called setForce() but this name
* was misunderstood to implement native git's --force option, which is not
* true.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
index 5f8c2b728a..a353d1a135 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
@@ -9,11 +9,12 @@
*/
package org.eclipse.jgit.api;
+import static org.eclipse.jgit.api.CherryPickCommitMessageProvider.ORIGINAL;
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
import java.io.IOException;
import java.text.MessageFormat;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -62,10 +63,12 @@ import org.eclipse.jgit.treewalk.FileTreeIterator;
public class CherryPickCommand extends GitCommand<CherryPickResult> {
private String reflogPrefix = "cherry-pick:"; //$NON-NLS-1$
- private List<Ref> commits = new LinkedList<>();
+ private List<Ref> commits = new ArrayList<>();
private String ourCommitName = null;
+ private CherryPickCommitMessageProvider messageProvider = ORIGINAL;
+
private MergeStrategy strategy = MergeStrategy.RECURSIVE;
private ContentMergeStrategy contentStrategy;
@@ -99,7 +102,7 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> {
UnmergedPathsException, ConcurrentRefUpdateException,
WrongRepositoryStateException, NoHeadException {
RevCommit newHead = null;
- List<Ref> cherryPickedRefs = new LinkedList<>();
+ List<Ref> cherryPickedRefs = new ArrayList<>();
checkCallable();
try (RevWalk revWalk = new RevWalk(repo)) {
@@ -168,8 +171,10 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> {
dco.checkout();
if (!noCommit) {
try (Git git = new Git(getRepository())) {
+ String commitMessage = messageProvider
+ .getCherryPickedCommitMessage(srcCommit);
newHead = git.commit()
- .setMessage(srcCommit.getFullMessage())
+ .setMessage(commitMessage)
.setReflogComment(reflogPrefix + " " //$NON-NLS-1$
+ srcCommit.getShortMessage())
.setAuthor(srcCommit.getAuthorIdent())
@@ -297,6 +302,22 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> {
}
/**
+ * Set a message provider for a target cherry-picked commit<br>
+ * By default original commit message is used (see
+ * {@link CherryPickCommitMessageProvider#ORIGINAL})
+ *
+ * @param messageProvider
+ * the commit message provider
+ * @return {@code this}
+ * @since 6.9
+ */
+ public CherryPickCommand setCherryPickCommitMessageProvider(
+ CherryPickCommitMessageProvider messageProvider) {
+ this.messageProvider = messageProvider;
+ return this;
+ }
+
+ /**
* Set the prefix to use in the reflog.
* <p>
* This is primarily needed for implementing rebase in terms of
@@ -399,7 +420,6 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> {
return headName;
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommitMessageProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommitMessageProvider.java
new file mode 100644
index 0000000000..50d65b6fa8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommitMessageProvider.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2023 Dmitrii Naumenko <dmitrii.naumenko@jetbrains.com>
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is aailable at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.api;
+
+import java.util.List;
+
+import org.eclipse.jgit.revwalk.FooterLine;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+/**
+ * The interface is used to construct a cherry-picked commit message based on
+ * the original commit
+ *
+ * @see #ORIGINAL
+ * @see #ORIGINAL_WITH_REFERENCE
+ * @since 6.9
+ */
+public interface CherryPickCommitMessageProvider {
+
+ /**
+ * This provider returns the original commit message
+ */
+ static final CherryPickCommitMessageProvider ORIGINAL = RevCommit::getFullMessage;
+
+ /**
+ * This provider returns the original commit message with original commit
+ * hash in SHA-1 form.<br>
+ * Example:
+ *
+ * <pre>
+ * <code>my original commit message
+ *
+ * (cherry picked from commit 75355897dc28e9975afed028c1a6d8c6b97b2a3c)</code>
+ * </pre>
+ *
+ * This is similar to <code>-x</code> flag in git-scm (see <a href=
+ * "https://git-scm.com/docs/git-cherry-pick#_options">https://git-scm.com/docs/git-cherry-pick#_options</a>)
+ */
+ static final CherryPickCommitMessageProvider ORIGINAL_WITH_REFERENCE = srcCommit -> {
+ String fullMessage = srcCommit.getFullMessage();
+
+ // Don't add extra new line after footer (aka trailer)
+ // https://stackoverflow.com/questions/70007405/git-log-exclude-cherry-pick-messages-for-trailers
+ // https://lore.kernel.org/git/7vmx136cdc.fsf@alter.siamese.dyndns.org
+ String separator = messageEndsWithFooter(srcCommit) ? "\n" : "\n\n"; //$NON-NLS-1$//$NON-NLS-2$
+ String revisionString = srcCommit.getName();
+ return String.format("%s%s(cherry picked from commit %s)", //$NON-NLS-1$
+ fullMessage, separator, revisionString);
+ };
+
+ /**
+ * @param srcCommit
+ * original cherry-picked commit
+ * @return target cherry-picked commit message
+ */
+ String getCherryPickedCommitMessage(RevCommit srcCommit);
+
+ private static boolean messageEndsWithFooter(RevCommit srcCommit) {
+ byte[] rawBuffer = srcCommit.getRawBuffer();
+ List<FooterLine> footers = srcCommit.getFooterLines();
+ int maxFooterEnd = footers.stream().mapToInt(FooterLine::getEndOffset)
+ .max().orElse(-1);
+ return rawBuffer.length == maxFooterEnd;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java
index 55d86b7402..995f890389 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java
@@ -25,21 +25,21 @@ public class CherryPickResult {
* The cherry-pick status
*/
public enum CherryPickStatus {
- /** */
+ /** Cherry-pick succeeded */
OK {
@Override
public String toString() {
return "Ok"; //$NON-NLS-1$
}
},
- /** */
+ /** Cherry-pick failed */
FAILED {
@Override
public String toString() {
return "Failed"; //$NON-NLS-1$
}
},
- /** */
+ /** Cherry-pick found conflicts to be resolved */
CONFLICTING {
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
index 36ca97d694..a4a0c49f45 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
@@ -113,24 +113,25 @@ public class CleanCommand extends GitCommand<Set<String>> {
}
/**
- * When dryRun is false, deletes the specified path from disk. If dryRun
- * is true, no paths are actually deleted. In both cases, the paths that
- * would have been deleted are added to inFiles and returned.
+ * When dryRun is false, deletes the specified path from disk. If dryRun is
+ * true, no paths are actually deleted. In both cases, the paths that would
+ * have been deleted are added to inFiles and returned.
*
* Paths that are directories are recursively deleted when
- * {@link #directories} is true.
- * Paths that are git repositories are recursively deleted when
- * {@link #directories} and {@link #force} are both true.
+ * {@link #directories} is true. Paths that are git repositories are
+ * recursively deleted when {@link #directories} and {@link #force} are both
+ * true.
*
* @param path
- * The path to be cleaned
+ * The path to be cleaned
* @param inFiles
- * A set of strings representing the files that have been cleaned
- * already, the path to be cleaned will be added to this set
- * before being returned.
+ * A set of strings representing the files that have been cleaned
+ * already, the path to be cleaned will be added to this set
+ * before being returned.
*
* @return a set of strings with the cleaned path added to it
* @throws IOException
+ * if an IO error occurred
*/
private Set<String> cleanPath(String path, Set<String> inFiles)
throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
index 107b00e274..4a536b9534 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -29,6 +29,7 @@ import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.util.ShutdownHook;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode;
import org.eclipse.jgit.lib.ConfigConstants;
@@ -66,6 +67,8 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
private boolean bare;
+ private boolean relativePaths;
+
private FS fs;
private String remote = Constants.DEFAULT_REMOTE_NAME;
@@ -100,6 +103,8 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
private List<String> shallowExcludes = new ArrayList<>();
+ private ShutdownHook.Listener shutdownListener = this::cleanup;
+
private enum FETCH_TYPE {
MULTIPLE_BRANCHES, ALL_BRANCHES, MIRROR
}
@@ -181,12 +186,7 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
@SuppressWarnings("resource") // Closed by caller
Repository repository = init();
FetchResult fetchResult = null;
- Thread cleanupHook = new Thread(() -> cleanup());
- try {
- Runtime.getRuntime().addShutdownHook(cleanupHook);
- } catch (IllegalStateException e) {
- // ignore - the VM is already shutting down
- }
+ ShutdownHook.INSTANCE.register(shutdownListener);
try {
fetchResult = fetch(repository, u);
} catch (IOException ioe) {
@@ -210,11 +210,7 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
cleanup();
throw e;
} finally {
- try {
- Runtime.getRuntime().removeShutdownHook(cleanupHook);
- } catch (IllegalStateException e) {
- // ignore - the VM is already shutting down
- }
+ ShutdownHook.INSTANCE.unregister(shutdownListener);
}
try {
checkout(repository, fetchResult);
@@ -270,6 +266,7 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
private Repository init() throws GitAPIException {
InitCommand command = Git.init();
command.setBare(bare);
+ command.setRelativeDirs(relativePaths);
if (fs != null) {
command.setFs(fs);
}
@@ -561,6 +558,20 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
}
/**
+ * Set whether the cloned repository shall use relative paths for GIT_DIR
+ * and GIT_WORK_TREE
+ *
+ * @param relativePaths
+ * if true, use relative paths for GIT_DIR and GIT_WORK_TREE
+ * @return this instance
+ * @since 7.2
+ */
+ public CloneCommand setRelativePaths(boolean relativePaths) {
+ this.relativePaths = relativePaths;
+ return this;
+ }
+
+ /**
* Set the file system abstraction to be used for repositories created by
* this command.
*
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
index 3b3baf5a12..a7d409c3f5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -16,7 +16,6 @@ import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.List;
import org.eclipse.jgit.annotations.NonNull;
@@ -52,9 +51,6 @@ import org.eclipse.jgit.lib.CommitConfig.CleanupMode;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.GpgConfig;
-import org.eclipse.jgit.lib.GpgConfig.GpgFormat;
-import org.eclipse.jgit.lib.GpgObjectSigner;
-import org.eclipse.jgit.lib.GpgSigner;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
@@ -63,6 +59,8 @@ import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryState;
+import org.eclipse.jgit.lib.Signer;
+import org.eclipse.jgit.lib.Signers;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTag;
@@ -109,7 +107,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
* parents this commit should have. The current HEAD will be in this list
* and also all commits mentioned in .git/MERGE_HEAD
*/
- private List<ObjectId> parents = new LinkedList<>();
+ private List<ObjectId> parents = new ArrayList<>();
private String reflogComment;
@@ -130,7 +128,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
private String signingKey;
- private GpgSigner gpgSigner;
+ private Signer signer;
private GpgConfig gpgConfig;
@@ -320,30 +318,22 @@ public class CommitCommand extends GitCommand<RevCommit> {
}
}
- private void sign(CommitBuilder commit) throws ServiceUnavailableException,
- CanceledException, UnsupportedSigningFormatException {
- if (gpgSigner == null) {
- gpgSigner = GpgSigner.getDefault();
- if (gpgSigner == null) {
- throw new ServiceUnavailableException(
- JGitText.get().signingServiceUnavailable);
+ private void sign(CommitBuilder commit)
+ throws CanceledException, IOException,
+ UnsupportedSigningFormatException {
+ if (signer == null) {
+ signer = Signers.get(gpgConfig.getKeyFormat());
+ if (signer == null) {
+ throw new UnsupportedSigningFormatException(MessageFormat
+ .format(JGitText.get().signatureTypeUnknown,
+ gpgConfig.getKeyFormat().toConfigValue()));
}
}
if (signingKey == null) {
signingKey = gpgConfig.getSigningKey();
}
- if (gpgSigner instanceof GpgObjectSigner) {
- ((GpgObjectSigner) gpgSigner).signObject(commit,
- signingKey, committer, credentialsProvider,
- gpgConfig);
- } else {
- if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) {
- throw new UnsupportedSigningFormatException(JGitText
- .get().onlyOpenPgpSupportedForSigning);
- }
- gpgSigner.sign(commit, signingKey, committer,
- credentialsProvider);
- }
+ signer.signObject(repo, gpgConfig, commit, committer, signingKey,
+ credentialsProvider);
}
private void updateRef(RepositoryState state, ObjectId headId,
@@ -614,7 +604,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
// specified the commit should not be empty. This behaviour differs
// from native git but can only be adapted in the next release.
// TODO(ch) align the defaults with native git
- allowEmpty = (only.isEmpty()) ? Boolean.TRUE : Boolean.FALSE;
+ allowEmpty = only.isEmpty() ? Boolean.TRUE : Boolean.FALSE;
// when doing a merge commit parse MERGE_HEAD and MERGE_MSG files
if (state == RepositoryState.MERGING_RESOLVED
@@ -1098,22 +1088,22 @@ public class CommitCommand extends GitCommand<RevCommit> {
}
/**
- * Sets the {@link GpgSigner} to use if the commit is to be signed.
+ * Sets the {@link Signer} to use if the commit is to be signed.
*
* @param signer
* to use; if {@code null}, the default signer will be used
* @return {@code this}
- * @since 5.11
+ * @since 7.0
*/
- public CommitCommand setGpgSigner(GpgSigner signer) {
+ public CommitCommand setSigner(Signer signer) {
checkCallable();
- this.gpgSigner = signer;
+ this.signer = signer;
return this;
}
/**
* Sets an external {@link GpgConfig} to use. Whether it will be used is at
- * the discretion of the {@link #setGpgSigner(GpgSigner)}.
+ * the discretion of the {@link #setSigner(Signer)}.
*
* @param config
* to set; if {@code null}, the config will be loaded from the
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java
index 9e77dd7348..013e0ff131 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java
@@ -82,16 +82,13 @@ public class CreateBranchCommand extends GitCommand<Ref> {
super(repo);
}
- /** {@inheritDoc} */
@Override
public Ref call() throws GitAPIException, RefAlreadyExistsException,
RefNotFoundException, InvalidRefNameException {
checkCallable();
processOptions();
try (RevWalk revWalk = new RevWalk(repo)) {
- Ref refToCheck = repo.findRef(name);
- boolean exists = refToCheck != null
- && refToCheck.getName().startsWith(R_HEADS);
+ boolean exists = repo.findRef(R_HEADS + name) != null;
if (!force && exists)
throw new RefAlreadyExistsException(MessageFormat.format(
JGitText.get().refAlreadyExists1, name));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java
index 3d3ee63568..a5c535f772 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
- * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com> and others
+ * Copyright (C) 2010, 2023 Chris Aniszczyk <caniszczyk@gmail.com> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -14,6 +14,7 @@ import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -25,6 +26,8 @@ import org.eclipse.jgit.api.errors.NotMergedException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.ConfigConstants;
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.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
@@ -47,8 +50,11 @@ import org.eclipse.jgit.revwalk.RevWalk;
* >Git documentation about Branch</a>
*/
public class DeleteBranchCommand extends GitCommand<List<String>> {
+
private final Set<String> branchNames = new HashSet<>();
+ private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
+
private boolean force;
/**
@@ -61,14 +67,34 @@ public class DeleteBranchCommand extends GitCommand<List<String>> {
super(repo);
}
- /** {@inheritDoc} */
@Override
public List<String> call() throws GitAPIException,
NotMergedException, CannotDeleteCurrentBranchException {
checkCallable();
List<String> result = new ArrayList<>();
- if (branchNames.isEmpty())
+ Set<String> shortNames = new HashSet<>();
+ if (branchNames.isEmpty()) {
return result;
+ }
+ Exception error = null;
+ try {
+ deleteBranches(result, shortNames);
+ } catch (Exception e) {
+ error = e;
+ }
+ monitor.beginTask(JGitText.get().updatingConfig, 1);
+ try {
+ updateConfig(shortNames, error);
+ } finally {
+ monitor.update(1);
+ monitor.endTask();
+ }
+ return result;
+ }
+
+ private void deleteBranches(List<String> result,
+ Set<String> shortNames) throws GitAPIException, NotMergedException,
+ CannotDeleteCurrentBranchException {
try {
String currentBranch = repo.getFullBranch();
if (!force) {
@@ -78,12 +104,13 @@ public class DeleteBranchCommand extends GitCommand<List<String>> {
RevCommit tip = walk
.parseCommit(repo.resolve(Constants.HEAD));
for (String branchName : branchNames) {
- if (branchName == null)
+ if (branchName == null) {
continue;
+ }
Ref currentRef = repo.findRef(branchName);
- if (currentRef == null)
+ if (currentRef == null) {
continue;
-
+ }
RevCommit base = walk
.parseCommit(repo.resolve(branchName));
if (!walk.isMergedInto(base, tip)) {
@@ -93,58 +120,105 @@ public class DeleteBranchCommand extends GitCommand<List<String>> {
}
}
setCallable(false);
- for (String branchName : branchNames) {
- if (branchName == null)
- continue;
- Ref currentRef = repo.findRef(branchName);
- if (currentRef == null)
- continue;
- String fullName = currentRef.getName();
- if (fullName.equals(currentBranch))
- throw new CannotDeleteCurrentBranchException(
- MessageFormat
- .format(
- JGitText.get().cannotDeleteCheckedOutBranch,
- branchName));
- RefUpdate update = repo.updateRef(fullName);
- update.setRefLogMessage("branch deleted", false); //$NON-NLS-1$
- update.setForceUpdate(true);
- Result deleteResult = update.delete();
-
- boolean ok = true;
- switch (deleteResult) {
- case IO_FAILURE:
- case LOCK_FAILURE:
- case REJECTED:
- ok = false;
- break;
- default:
- break;
- }
+ monitor.start(2);
+ monitor.beginTask(JGitText.get().deletingBranches,
+ branchNames.size());
+ try {
+ for (String branchName : branchNames) {
+ if (branchName == null) {
+ monitor.update(1);
+ continue;
+ }
+ Ref currentRef = repo.findRef(branchName);
+ if (currentRef == null) {
+ monitor.update(1);
+ continue;
+ }
+ String fullName = currentRef.getName();
+ if (fullName.equals(currentBranch)) {
+ throw new CannotDeleteCurrentBranchException(
+ MessageFormat.format(JGitText
+ .get().cannotDeleteCheckedOutBranch,
+ branchName));
+ }
+ RefUpdate update = repo.updateRef(fullName);
+ update.setRefLogMessage("branch deleted", false); //$NON-NLS-1$
+ update.setForceUpdate(true);
+ Result deleteResult = update.delete();
- if (ok) {
- result.add(fullName);
- if (fullName.startsWith(Constants.R_HEADS)) {
- String shortenedName = fullName
- .substring(Constants.R_HEADS.length());
- // remove upstream configuration if any
- final StoredConfig cfg = repo.getConfig();
- cfg.unsetSection(
- ConfigConstants.CONFIG_BRANCH_SECTION,
- shortenedName);
- cfg.save();
+ switch (deleteResult) {
+ case IO_FAILURE:
+ case LOCK_FAILURE:
+ case REJECTED:
+ throw new JGitInternalException(MessageFormat.format(
+ JGitText.get().deleteBranchUnexpectedResult,
+ deleteResult.name()));
+ default:
+ result.add(fullName);
+ if (fullName.startsWith(Constants.R_HEADS)) {
+ shortNames.add(fullName
+ .substring(Constants.R_HEADS.length()));
+ }
+ break;
+ }
+ monitor.update(1);
+ if (monitor.isCancelled()) {
+ break;
}
- } else
- throw new JGitInternalException(MessageFormat.format(
- JGitText.get().deleteBranchUnexpectedResult,
- deleteResult.name()));
+ }
+ } finally {
+ monitor.endTask();
}
- return result;
} catch (IOException ioe) {
throw new JGitInternalException(ioe.getMessage(), ioe);
}
}
+ private void updateConfig(Set<String> shortNames, Exception error)
+ throws GitAPIException {
+ IOException configError = null;
+ if (!shortNames.isEmpty()) {
+ try {
+ // Remove upstream configurations if any
+ StoredConfig cfg = repo.getConfig();
+ boolean changed = false;
+ for (String branchName : shortNames) {
+ changed |= cfg.removeSection(
+ ConfigConstants.CONFIG_BRANCH_SECTION,
+ branchName);
+ }
+ if (changed) {
+ cfg.save();
+ }
+ } catch (IOException e) {
+ configError = e;
+ }
+ }
+ if (error == null) {
+ if (configError != null) {
+ throw new JGitInternalException(configError.getMessage(),
+ configError);
+ }
+ } else if (error instanceof GitAPIException) {
+ if (configError != null) {
+ error.addSuppressed(configError);
+ }
+ throw (GitAPIException) error;
+ } else if (error instanceof RuntimeException) {
+ if (configError != null) {
+ error.addSuppressed(configError);
+ }
+ throw (RuntimeException) error;
+ } else {
+ JGitInternalException internal = new JGitInternalException(
+ error.getMessage(), error);
+ if (configError != null) {
+ internal.addSuppressed(configError);
+ }
+ throw internal;
+ }
+ }
+
/**
* Set the names of the branches to delete
*
@@ -161,6 +235,22 @@ public class DeleteBranchCommand extends GitCommand<List<String>> {
}
/**
+ * Sets the names of the branches to delete
+ *
+ * @param branchNames
+ * the names of the branches to delete; if not set, this will do
+ * nothing; invalid branch names will simply be ignored
+ * @return {@code this}
+ * @since 6.8
+ */
+ public DeleteBranchCommand setBranchNames(Collection<String> branchNames) {
+ checkCallable();
+ this.branchNames.clear();
+ this.branchNames.addAll(branchNames);
+ return this;
+ }
+
+ /**
* Set whether to forcefully delete branches
*
* @param force
@@ -176,4 +266,34 @@ public class DeleteBranchCommand extends GitCommand<List<String>> {
this.force = force;
return this;
}
+
+ /**
+ * Retrieves the progress monitor.
+ *
+ * @return the {@link ProgressMonitor} for the delete operation
+ * @since 6.8
+ */
+ public ProgressMonitor getProgressMonitor() {
+ return monitor;
+ }
+
+ /**
+ * Sets the progress monitor associated with the delete operation. By
+ * default, this is set to <code>NullProgressMonitor</code>
+ *
+ * @see NullProgressMonitor
+ * @param monitor
+ * a {@link ProgressMonitor}
+ * @return {@code this}
+ * @since 6.8
+ */
+ public DeleteBranchCommand setProgressMonitor(ProgressMonitor monitor) {
+ checkCallable();
+ if (monitor == null) {
+ monitor = NullProgressMonitor.INSTANCE;
+ }
+ this.monitor = monitor;
+ return this;
+ }
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java
index 64d0d94171..92d88aad7d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteTagCommand.java
@@ -48,7 +48,6 @@ public class DeleteTagCommand extends GitCommand<List<String>> {
super(repo);
}
- /** {@inheritDoc} */
@Override
public List<String> call() throws GitAPIException {
checkCallable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
index 805a886392..d2526287f9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
@@ -15,11 +15,11 @@ import static org.eclipse.jgit.lib.TypedConfigGetter.UNSET_INT;
import java.io.IOException;
import java.text.MessageFormat;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
-import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -76,6 +76,11 @@ public class DescribeCommand extends GitCommand<String> {
private List<FileNameMatcher> matchers = new ArrayList<>();
/**
+ * Pattern matchers to be applied to tags for exclusion.
+ */
+ private List<FileNameMatcher> excludeMatchers = new ArrayList<>();
+
+ /**
* Whether to use all refs in the refs/ namespace
*/
private boolean useAll;
@@ -263,6 +268,27 @@ public class DescribeCommand extends GitCommand<String> {
return this;
}
+ /**
+ * Sets one or more {@code glob(7)} patterns that tags must not match to be
+ * considered. If multiple patterns are provided, they will all be applied.
+ *
+ * @param patterns
+ * the {@code glob(7)} pattern or patterns
+ * @return {@code this}
+ * @throws org.eclipse.jgit.errors.InvalidPatternException
+ * if the pattern passed in was invalid.
+ * @see <a href=
+ * "https://www.kernel.org/pub/software/scm/git/docs/git-describe.html"
+ * >Git documentation about describe</a>
+ * @since 7.2
+ */
+ public DescribeCommand setExclude(String... patterns) throws InvalidPatternException {
+ for (String p : patterns) {
+ excludeMatchers.add(new FileNameMatcher(p, null));
+ }
+ return this;
+ }
+
private final Comparator<Ref> TAG_TIE_BREAKER = new Comparator<>() {
@Override
@@ -274,25 +300,28 @@ public class DescribeCommand extends GitCommand<String> {
}
}
- private Date tagDate(Ref tag) throws IOException {
+ private Instant tagDate(Ref tag) throws IOException {
RevTag t = w.parseTag(tag.getObjectId());
w.parseBody(t);
- return t.getTaggerIdent().getWhen();
+ return t.getTaggerIdent().getWhenAsInstant();
}
};
private Optional<Ref> getBestMatch(List<Ref> tags) {
if (tags == null || tags.isEmpty()) {
return Optional.empty();
- } else if (matchers.isEmpty()) {
+ } else if (matchers.isEmpty() && excludeMatchers.isEmpty()) {
Collections.sort(tags, TAG_TIE_BREAKER);
return Optional.of(tags.get(0));
- } else {
+ }
+
+ Stream<Ref> matchingTags;
+ if (!matchers.isEmpty()) {
// Find the first tag that matches in the stream of all tags
// filtered by matchers ordered by tie break order
- Stream<Ref> matchingTags = Stream.empty();
+ matchingTags = Stream.empty();
for (FileNameMatcher matcher : matchers) {
- Stream<Ref> m = tags.stream().filter(
+ Stream<Ref> m = tags.stream().filter( //
tag -> {
matcher.append(formatRefName(tag.getName()));
boolean result = matcher.isMatch();
@@ -301,8 +330,22 @@ public class DescribeCommand extends GitCommand<String> {
});
matchingTags = Stream.of(matchingTags, m).flatMap(i -> i);
}
- return matchingTags.sorted(TAG_TIE_BREAKER).findFirst();
+ } else {
+ // If there are no matchers, there are only excluders
+ // Assume all tags match for now before applying excluders
+ matchingTags = tags.stream();
+ }
+
+ for (FileNameMatcher matcher : excludeMatchers) {
+ matchingTags = matchingTags.filter( //
+ tag -> {
+ matcher.append(formatRefName(tag.getName()));
+ boolean result = matcher.isMatch();
+ matcher.reset();
+ return !result;
+ });
}
+ return matchingTags.sorted(TAG_TIE_BREAKER).findFirst();
}
private ObjectId getObjectIdFromRef(Ref r) throws JGitInternalException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
index 3c772c2765..f24127bd51 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
@@ -124,7 +124,7 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> {
FetchRecurseSubmodulesMode mode = repo.getConfig().getEnum(
FetchRecurseSubmodulesMode.values(),
ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
- ConfigConstants.CONFIG_KEY_FETCH_RECURSE_SUBMODULES, null);
+ ConfigConstants.CONFIG_KEY_FETCH_RECURSE_SUBMODULES);
if (mode != null) {
return mode;
}
@@ -132,7 +132,7 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> {
// Fall back to fetch.recurseSubmodules, if set
mode = repo.getConfig().getEnum(FetchRecurseSubmodulesMode.values(),
ConfigConstants.CONFIG_FETCH_SECTION, null,
- ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES, null);
+ ConfigConstants.CONFIG_KEY_RECURSE_SUBMODULES);
if (mode != null) {
return mode;
}
@@ -491,13 +491,13 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> {
*
* Default setting is Transport.DEFAULT_FETCH_THIN
*
- * @param thin
+ * @param thinPack
* the thin-pack preference
* @return {@code this}
*/
- public FetchCommand setThin(boolean thin) {
+ public FetchCommand setThin(boolean thinPack) {
checkCallable();
- this.thin = thin;
+ this.thin = thinPack;
return this;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
index 584d2bc394..f6935e1c67 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
@@ -12,6 +12,7 @@ package org.eclipse.jgit.api;
import java.io.IOException;
import java.text.MessageFormat;
import java.text.ParseException;
+import java.time.Instant;
import java.util.Date;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
@@ -59,10 +60,12 @@ public class GarbageCollectCommand extends GitCommand<Properties> {
private ProgressMonitor monitor;
- private Date expire;
+ private Instant expire;
private PackConfig pconfig;
+ private Boolean packKeptObjects;
+
/**
* Constructor for GarbageCollectCommand.
*
@@ -96,8 +99,29 @@ public class GarbageCollectCommand extends GitCommand<Properties> {
* @param expire
* minimal age of objects to be pruned.
* @return this instance
+ * @deprecated use {@link #setExpire(Instant)} instead
*/
+ @Deprecated(since = "7.2")
public GarbageCollectCommand setExpire(Date expire) {
+ if (expire != null) {
+ this.expire = expire.toInstant();
+ }
+ return this;
+ }
+
+ /**
+ * During gc() or prune() each unreferenced, loose object which has been
+ * created or modified after <code>expire</code> will not be pruned. Only
+ * older objects may be pruned. If set to null then every object is a
+ * candidate for pruning. Use {@link org.eclipse.jgit.util.GitTimeParser} to
+ * parse time formats used by git gc.
+ *
+ * @param expire
+ * minimal age of objects to be pruned.
+ * @return this instance
+ * @since 7.2
+ */
+ public GarbageCollectCommand setExpire(Instant expire) {
this.expire = expire;
return this;
}
@@ -106,8 +130,8 @@ public class GarbageCollectCommand extends GitCommand<Properties> {
* Whether to use aggressive mode or not. If set to true JGit behaves more
* similar to native git's "git gc --aggressive". If set to
* <code>true</code> compressed objects found in old packs are not reused
- * but every object is compressed again. Configuration variables
- * pack.window and pack.depth are set to 250 for this GC.
+ * but every object is compressed again. Configuration variables pack.window
+ * and pack.depth are set to 250 for this GC.
*
* @since 3.6
* @param aggressive
@@ -132,6 +156,19 @@ public class GarbageCollectCommand extends GitCommand<Properties> {
}
/**
+ * Whether to include objects in `.keep` packs when repacking.
+ *
+ * @param packKeptObjects
+ * whether to include objects in `.keep` files when repacking.
+ * @return this instance
+ * @since 5.13.3
+ */
+ public GarbageCollectCommand setPackKeptObjects(boolean packKeptObjects) {
+ this.packKeptObjects = Boolean.valueOf(packKeptObjects);
+ return this;
+ }
+
+ /**
* Whether to preserve old pack files instead of deleting them.
*
* @since 4.7
@@ -163,7 +200,6 @@ public class GarbageCollectCommand extends GitCommand<Properties> {
return this;
}
- /** {@inheritDoc} */
@Override
public Properties call() throws GitAPIException {
checkCallable();
@@ -175,7 +211,9 @@ public class GarbageCollectCommand extends GitCommand<Properties> {
gc.setProgressMonitor(monitor);
if (this.expire != null)
gc.setExpire(expire);
-
+ if (this.packKeptObjects != null) {
+ gc.setPackKeptObjects(packKeptObjects.booleanValue());
+ }
try {
gc.gc().get();
return toProperties(gc.getStatistics());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
index 3b3e10e7b2..a8b866d25b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java
@@ -68,6 +68,7 @@ public class Git implements AutoCloseable {
* @return a {@link org.eclipse.jgit.api.Git} object for the existing git
* repository
* @throws java.io.IOException
+ * if an IO error occurred
*/
public static Git open(File dir) throws IOException {
return open(dir, FS.DETECTED);
@@ -84,6 +85,7 @@ public class Git implements AutoCloseable {
* @return a {@link org.eclipse.jgit.api.Git} object for the existing git
* repository. Closing this instance will close the repo.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public static Git open(File dir, FS fs) throws IOException {
RepositoryCache.FileKey key;
@@ -199,7 +201,24 @@ public class Git implements AutoCloseable {
this(repo, false);
}
- Git(Repository repo, boolean closeRepo) {
+ /**
+ * Construct a new {@link org.eclipse.jgit.api.Git} object which can
+ * interact with the specified git repository.
+ * <p>
+ * All command classes returned by methods of this class will always
+ * interact with this git repository.
+ * <p>
+ * If {@code closeRepo = false} the caller is responsible for closing the
+ * repository.
+ *
+ * @param repo
+ * the git repository this class is interacting with;
+ * {@code null} is not allowed.
+ * @param closeRepo
+ * whether to close the repository when this instance is closed
+ * @since 7.4
+ */
+ public Git(Repository repo, boolean closeRepo) {
this.repo = requireNonNull(repo);
this.closeRepo = closeRepo;
}
@@ -712,6 +731,16 @@ public class Git implements AutoCloseable {
}
/**
+ * Return a command object to execute a {@code PackRefs} command
+ *
+ * @return a {@link org.eclipse.jgit.api.PackRefsCommand}
+ * @since 7.1
+ */
+ public PackRefsCommand packRefs() {
+ return new PackRefsCommand(repo);
+ }
+
+ /**
* Return a command object to find human-readable names of revisions.
*
* @return a {@link org.eclipse.jgit.api.NameRevCommand}.
@@ -792,7 +821,6 @@ public class Git implements AutoCloseable {
return repo;
}
- /** {@inheritDoc} */
@Override
public String toString() {
return "Git[" + repo + "]"; //$NON-NLS-1$//$NON-NLS-2$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java
index 240290f4f9..1da71aa6eb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java
@@ -19,6 +19,7 @@ import org.eclipse.jgit.api.errors.InvalidRefNameException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
@@ -44,6 +45,8 @@ public class InitCommand implements Callable<Git> {
private String initialBranch;
+ private boolean relativePaths;
+
/**
* {@inheritDoc}
* <p>
@@ -100,7 +103,11 @@ public class InitCommand implements Callable<Git> {
: initialBranch);
Repository repository = builder.build();
if (!repository.getObjectDatabase().exists())
- repository.create(bare);
+ if (repository instanceof FileRepository) {
+ ((FileRepository) repository).create(bare, relativePaths);
+ } else {
+ repository.create(bare);
+ }
return new Git(repository, true);
} catch (IOException | ConfigInvalidException e) {
throw new JGitInternalException(e.getMessage(), e);
@@ -214,4 +221,18 @@ public class InitCommand implements Callable<Git> {
this.initialBranch = branch;
return this;
}
+
+ /**
+ * * Set whether the repository shall use relative paths for GIT_DIR and
+ * GIT_WORK_TREE
+ *
+ * @param relativePaths
+ * if true, use relative paths for GIT_DIR and GIT_WORK_TREE
+ * @return {@code this}
+ * @since 7.2
+ */
+ public InitCommand setRelativeDirs(boolean relativePaths) {
+ this.relativePaths = relativePaths;
+ return this;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java
index 4b7445fc95..e3c3c89bcb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java
@@ -73,7 +73,6 @@ public class ListBranchCommand extends GitCommand<List<Ref>> {
super(repo);
}
- /** {@inheritDoc} */
@Override
public List<Ref> call() throws GitAPIException {
checkCallable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java
index 34955e86f9..9eb52866dc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListNotesCommand.java
@@ -44,7 +44,6 @@ public class ListNotesCommand extends GitCommand<List<Note>> {
super(repo);
}
- /** {@inheritDoc} */
@Override
public List<Note> call() throws GitAPIException {
checkCallable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java
index 27a5288429..9a4a822b59 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListTagCommand.java
@@ -54,8 +54,11 @@ public class ListTagCommand extends GitCommand<List<Ref>> {
* the specified commit
* @return this command
* @throws IOException
+ * if an IO error occurred
* @throws IncorrectObjectTypeException
+ * if commit has an incorrect object type
* @throws MissingObjectException
+ * if the commit is missing
*
* @since 6.6
*/
@@ -67,7 +70,6 @@ public class ListTagCommand extends GitCommand<List<Ref>> {
return this;
}
- /** {@inheritDoc} */
@Override
public List<Ref> call() throws GitAPIException {
checkCallable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java
index fa40d93e52..2a8d34ed68 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java
@@ -53,13 +53,11 @@ import org.eclipse.jgit.treewalk.filter.TreeFilter;
* </pre>
* <p>
*
- * <p>
* Get commits only for a specific file:
*
* <pre>
* git.log().add(head).addPath(&quot;dir/filename.txt&quot;).call();
* </pre>
- * <p>
*
* @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-log.html"
* >Git documentation about Log</a>
@@ -112,10 +110,10 @@ public class LogCommand extends GitCommand<Iterable<RevCommit>> {
}
if (!filters.isEmpty()) {
if (filters.size() == 1) {
- filters.add(TreeFilter.ANY_DIFF);
+ walk.setTreeFilter(filters.get(0));
+ } else {
+ walk.setTreeFilter(AndTreeFilter.create(filters));
}
- walk.setTreeFilter(AndTreeFilter.create(filters));
-
}
if (skip > -1 && maxCount > -1)
walk.setRevFilter(AndRevFilter.create(SkipRevFilter.create(skip),
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
index ed4a5342b3..7064f5a57a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
@@ -13,9 +13,9 @@ package org.eclipse.jgit.api;
import java.io.IOException;
import java.text.MessageFormat;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -75,7 +75,7 @@ public class MergeCommand extends GitCommand<MergeResult> {
private ContentMergeStrategy contentStrategy;
- private List<Ref> commits = new LinkedList<>();
+ private List<Ref> commits = new ArrayList<>();
private Boolean squash;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java
index 7347f63889..0fa11f2cbb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeResult.java
@@ -31,7 +31,9 @@ public class MergeResult {
* The status the merge resulted in.
*/
public enum MergeStatus {
- /** */
+ /**
+ * Merge is a fast-forward
+ */
FAST_FORWARD {
@Override
public String toString() {
@@ -44,6 +46,8 @@ public class MergeResult {
}
},
/**
+ * Merge is a fast-forward, squashed
+ *
* @since 2.0
*/
FAST_FORWARD_SQUASHED {
@@ -57,7 +61,9 @@ public class MergeResult {
return true;
}
},
- /** */
+ /**
+ * Already up to date, merge was a no-op
+ */
ALREADY_UP_TO_DATE {
@Override
public String toString() {
@@ -69,7 +75,9 @@ public class MergeResult {
return true;
}
},
- /** */
+ /**
+ * Merge failed
+ */
FAILED {
@Override
public String toString() {
@@ -81,7 +89,9 @@ public class MergeResult {
return false;
}
},
- /** */
+ /**
+ * Merged
+ */
MERGED {
@Override
public String toString() {
@@ -94,6 +104,8 @@ public class MergeResult {
}
},
/**
+ * Merged, squashed, not updating HEAD
+ *
* @since 2.0
*/
MERGED_SQUASHED {
@@ -108,6 +120,8 @@ public class MergeResult {
}
},
/**
+ * Merged, squashed, not committed
+ *
* @since 3.0
*/
MERGED_SQUASHED_NOT_COMMITTED {
@@ -121,7 +135,9 @@ public class MergeResult {
return true;
}
},
- /** */
+ /**
+ * Merge raised conflicts to be resolved
+ */
CONFLICTING {
@Override
public String toString() {
@@ -134,6 +150,8 @@ public class MergeResult {
}
},
/**
+ * Merge was aborted
+ *
* @since 2.2
*/
ABORTED {
@@ -148,6 +166,8 @@ public class MergeResult {
}
},
/**
+ * Merged, not committed
+ *
* @since 3.0
**/
MERGED_NOT_COMMITTED {
@@ -161,7 +181,7 @@ public class MergeResult {
return true;
}
},
- /** */
+ /** Not yet supported */
NOT_SUPPORTED {
@Override
public String toString() {
@@ -191,6 +211,8 @@ public class MergeResult {
};
/**
+ * Whether the merge was successful
+ *
* @return whether the status indicates a successful result
*/
public abstract boolean isSuccessful();
@@ -364,7 +386,6 @@ public class MergeResult {
return base;
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java
index e6ab1e6588..1ff6e98b12 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java
@@ -102,7 +102,6 @@ public class NameRevCommand extends GitCommand<Map<ObjectId, String>> {
};
}
- /** {@inheritDoc} */
@Override
public Map<ObjectId, String> call() throws GitAPIException {
try {
@@ -349,7 +348,7 @@ public class NameRevCommand extends GitCommand<Map<ObjectId, String>> {
}
// Don't tiebreak if prefixes are the same, in order to prefer first-parent
// paths.
- return li - ri;
+ return (long) li - ri;
}
private static String simplify(String refName) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PackRefsCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PackRefsCommand.java
new file mode 100644
index 0000000000..29a69c5ac4
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PackRefsCommand.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2024 Qualcomm Innovation Center, 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 v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.api;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Optimize storage of references.
+ *
+ * @since 7.1
+ */
+public class PackRefsCommand extends GitCommand<String> {
+ private ProgressMonitor monitor;
+
+ private boolean all;
+
+ /**
+ * Creates a new {@link PackRefsCommand} instance with default values.
+ *
+ * @param repo
+ * the repository this command will be used on
+ */
+ public PackRefsCommand(Repository repo) {
+ super(repo);
+ this.monitor = NullProgressMonitor.INSTANCE;
+ }
+
+ /**
+ * Set progress monitor
+ *
+ * @param monitor
+ * a progress monitor
+ * @return this instance
+ */
+ public PackRefsCommand setProgressMonitor(ProgressMonitor monitor) {
+ this.monitor = monitor;
+ return this;
+ }
+
+ /**
+ * Specify whether to pack all the references.
+ *
+ * @param all
+ * if <code>true</code> all the loose refs will be packed
+ * @return this instance
+ */
+ public PackRefsCommand setAll(boolean all) {
+ this.all = all;
+ return this;
+ }
+
+ /**
+ * Whether to pack all the references
+ *
+ * @return whether to pack all the references
+ */
+ public boolean isAll() {
+ return all;
+ }
+
+ @Override
+ public String call() throws GitAPIException {
+ checkCallable();
+ try {
+ repo.getRefDatabase().packRefs(monitor, this);
+ return JGitText.get().packRefsSuccessful;
+ } catch (IOException e) {
+ throw new JGitInternalException(JGitText.get().packRefsFailed, e);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
index 83ae0fc9d4..4b2cee45c2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
@@ -533,9 +533,9 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> {
Config config) {
BranchRebaseMode mode = config.getEnum(BranchRebaseMode.values(),
ConfigConstants.CONFIG_BRANCH_SECTION,
- branchName, ConfigConstants.CONFIG_KEY_REBASE, null);
+ branchName, ConfigConstants.CONFIG_KEY_REBASE);
if (mode == null) {
- mode = config.getEnum(BranchRebaseMode.values(),
+ mode = config.getEnum(
ConfigConstants.CONFIG_PULL_SECTION, null,
ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.NONE);
}
@@ -549,7 +549,7 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> {
Config config = repo.getConfig();
Merge ffMode = config.getEnum(Merge.values(),
ConfigConstants.CONFIG_PULL_SECTION, null,
- ConfigConstants.CONFIG_KEY_FF, null);
+ ConfigConstants.CONFIG_KEY_FF);
return ffMode != null ? FastForwardMode.valueOf(ffMode) : null;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java
index cbb9cc2f78..fdc7f3f333 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullResult.java
@@ -89,7 +89,6 @@ public class PullResult {
return true;
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
index 2ed1c52fd7..e9d1a3213b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
@@ -614,13 +614,13 @@ public class PushCommand extends
*
* Default setting is Transport.DEFAULT_PUSH_THIN
*
- * @param thin
+ * @param thinPack
* the thin-pack preference value
* @return {@code this}
*/
- public PushCommand setThin(boolean thin) {
+ public PushCommand setThin(boolean thinPack) {
checkCallable();
- this.thin = thin;
+ this.thin = thinPack;
return this;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
index 1e5523f275..3ae7a6c81e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -18,12 +18,13 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.MessageFormat;
+import java.time.Instant;
+import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
@@ -86,7 +87,6 @@ import org.eclipse.jgit.util.RawParseUtils;
* supported options and arguments of this command and a {@link #call()} method
* to finally execute the command. Each instance of this class should only be
* used for one invocation of the command (means: one call to {@link #call()})
- * <p>
*
* @see <a
* href="http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html"
@@ -290,13 +290,17 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
}
}
RebaseResult res = initFilesAndRewind();
- if (stopAfterInitialization)
+ if (stopAfterInitialization) {
return RebaseResult.INTERACTIVE_PREPARED_RESULT;
+ }
if (res != null) {
- autoStashApply();
- if (rebaseState.getDir().exists())
+ if (!autoStashApply()) {
+ res = RebaseResult.STASH_APPLY_CONFLICTS_RESULT;
+ }
+ if (rebaseState.getDir().exists()) {
FileUtils.delete(rebaseState.getDir(),
FileUtils.RECURSIVE);
+ }
return res;
}
}
@@ -382,7 +386,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
}
private boolean autoStashApply() throws IOException, GitAPIException {
- boolean conflicts = false;
+ boolean success = true;
if (rebaseState.getFile(AUTOSTASH).exists()) {
String stash = rebaseState.readFile(AUTOSTASH);
try (Git git = Git.wrap(repo)) {
@@ -390,7 +394,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
.ignoreRepositoryState(true).setStrategy(strategy)
.call();
} catch (StashApplyFailureException e) {
- conflicts = true;
+ success = false;
try (RevWalk rw = new RevWalk(repo)) {
ObjectId stashId = repo.resolve(stash);
RevCommit commit = rw.parseCommit(stashId);
@@ -399,7 +403,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
}
}
}
- return conflicts;
+ return success;
}
private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent,
@@ -724,13 +728,15 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
boolean lastStepIsForward) throws IOException, GitAPIException {
String headName = rebaseState.readFile(HEAD_NAME);
updateHead(headName, finalHead, upstreamCommit);
- boolean stashConflicts = autoStashApply();
+ boolean unstashSuccessful = autoStashApply();
getRepository().autoGC(monitor);
FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE);
- if (stashConflicts)
+ if (!unstashSuccessful) {
return RebaseResult.STASH_APPLY_CONFLICTS_RESULT;
- if (lastStepIsForward || finalHead == null)
+ }
+ if (lastStepIsForward || finalHead == null) {
return RebaseResult.FAST_FORWARD_RESULT;
+ }
return RebaseResult.OK_RESULT;
}
@@ -1000,7 +1006,9 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
/**
* @return the commit if we had to do a commit, otherwise null
* @throws GitAPIException
+ * if JGit API failed
* @throws IOException
+ * if an IO error occurred
*/
private RevCommit continueRebase() throws GitAPIException, IOException {
// if there are still conflicts, we throw a specific Exception
@@ -1057,12 +1065,16 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
String authorScript = toAuthorScript(author);
rebaseState.createFile(AUTHOR_SCRIPT, authorScript);
rebaseState.createFile(MESSAGE, commitToPick.getFullMessage());
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- try (DiffFormatter df = new DiffFormatter(bos)) {
- df.setRepository(repo);
- df.format(commitToPick.getParent(0), commitToPick);
+ if (commitToPick.getParentCount() > 0) {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ try (DiffFormatter df = new DiffFormatter(bos)) {
+ df.setRepository(repo);
+ df.format(commitToPick.getParent(0), commitToPick);
+ }
+ rebaseState.createFile(PATCH, new String(bos.toByteArray(), UTF_8));
+ } else {
+ rebaseState.createFile(PATCH, ""); //$NON-NLS-1$
}
- rebaseState.createFile(PATCH, new String(bos.toByteArray(), UTF_8));
rebaseState.createFile(STOPPED_SHA,
repo.newObjectReader()
.abbreviate(
@@ -1102,13 +1114,15 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
* that can not be parsed as steps
*
* @param numSteps
+ * number of steps to remove
* @throws IOException
+ * if an IO error occurred
*/
private void popSteps(int numSteps) throws IOException {
if (numSteps == 0)
return;
- List<RebaseTodoLine> todoLines = new LinkedList<>();
- List<RebaseTodoLine> poppedLines = new LinkedList<>();
+ List<RebaseTodoLine> todoLines = new ArrayList<>();
+ List<RebaseTodoLine> poppedLines = new ArrayList<>();
for (RebaseTodoLine line : repo.readRebaseTodo(
rebaseState.getPath(GIT_REBASE_TODO), true)) {
@@ -1146,7 +1160,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
if (!isInteractive() && walk.isMergedInto(upstream, headCommit))
return RebaseResult.UP_TO_DATE_RESULT;
else if (!isInteractive() && walk.isMergedInto(headCommit, upstream)) {
- // head is already merged into upstream, fast-foward
+ // head is already merged into upstream, fast-forward
monitor.beginTask(MessageFormat.format(
JGitText.get().resettingHead,
upstreamCommit.getShortMessage()), ProgressMonitor.UNKNOWN);
@@ -1217,7 +1231,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
Iterator<RevCommit> commitsToUse = r.iterator();
while (commitsToUse.hasNext()) {
RevCommit commit = commitsToUse.next();
- if (preserveMerges || commit.getParentCount() == 1) {
+ if (preserveMerges || commit.getParentCount() <= 1) {
cherryPickList.add(commit);
}
}
@@ -1234,23 +1248,31 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
walk.markStart(upstreamCommit);
walk.markStart(headCommit);
RevCommit base;
- while ((base = walk.next()) != null)
+ while ((base = walk.next()) != null) {
RebaseState.createFile(rewrittenDir, base.getName(),
upstreamCommit.getName());
-
+ }
Iterator<RevCommit> iterator = cherryPickList.iterator();
pickLoop: while(iterator.hasNext()){
RevCommit commit = iterator.next();
- for (int i = 0; i < commit.getParentCount(); i++) {
- boolean parentRewritten = new File(rewrittenDir, commit
- .getParent(i).getName()).exists();
- if (parentRewritten) {
- new File(rewrittenDir, commit.getName()).createNewFile();
- continue pickLoop;
+ int nOfParents = commit.getParentCount();
+ if (nOfParents == 0) {
+ // Must be the very first commit in the cherryPickList. We
+ // have independent branches.
+ new File(rewrittenDir, commit.getName()).createNewFile();
+ } else {
+ for (int i = 0; i < nOfParents; i++) {
+ boolean parentRewritten = new File(rewrittenDir,
+ commit.getParent(i).getName()).exists();
+ if (parentRewritten) {
+ new File(rewrittenDir, commit.getName())
+ .createNewFile();
+ continue pickLoop;
+ }
}
+ // commit is only merged in, needs not be rewritten
+ iterator.remove();
}
- // commit is only merged in, needs not be rewritten
- iterator.remove();
}
}
return cherryPickList;
@@ -1289,7 +1311,9 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
* if we can fast-forward to.
* @return the new head, or null
* @throws java.io.IOException
+ * if an IO error occurred
* @throws org.eclipse.jgit.api.errors.GitAPIException
+ * if a JGit API exception occurred
*/
public RevCommit tryFastForward(RevCommit newCommit) throws IOException,
GitAPIException {
@@ -1434,13 +1458,14 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
throw new JGitInternalException(
JGitText.get().abortingRebaseFailed);
}
- boolean stashConflicts = autoStashApply();
+ boolean unstashSuccessful = autoStashApply();
// cleanup the files
FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE);
repo.writeCherryPickHead(null);
repo.writeMergeHeads(null);
- if (stashConflicts)
+ if (!unstashSuccessful) {
return RebaseResult.STASH_APPLY_CONFLICTS_RESULT;
+ }
return result;
} finally {
@@ -1540,6 +1565,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
* the name of the upstream branch
* @return {@code this}
* @throws org.eclipse.jgit.api.errors.RefNotFoundException
+ * if {@code upstream} Ref couldn't be resolved
*/
public RebaseCommand setUpstream(String upstream)
throws RefNotFoundException {
@@ -1811,23 +1837,26 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
// the time is saved as <seconds since 1970> <timezone offset>
int timeStart = 0;
- if (time.startsWith("@")) //$NON-NLS-1$
+ if (time.startsWith("@")) { //$NON-NLS-1$
timeStart = 1;
- else
+ } else {
timeStart = 0;
- long when = Long
- .parseLong(time.substring(timeStart, time.indexOf(' '))) * 1000;
+ }
+ Instant when = Instant.ofEpochSecond(
+ Long.parseLong(time.substring(timeStart, time.indexOf(' '))));
String tzOffsetString = time.substring(time.indexOf(' ') + 1);
int multiplier = -1;
- if (tzOffsetString.charAt(0) == '+')
+ if (tzOffsetString.charAt(0) == '+') {
multiplier = 1;
+ }
int hours = Integer.parseInt(tzOffsetString.substring(1, 3));
int minutes = Integer.parseInt(tzOffsetString.substring(3, 5));
// this is in format (+/-)HHMM (hours and minutes)
- // we need to convert into minutes
- int tz = (hours * 60 + minutes) * multiplier;
- if (name != null && email != null)
+ ZoneOffset tz = ZoneOffset.ofHoursMinutes(hours * multiplier,
+ minutes * multiplier);
+ if (name != null && email != null) {
return new PersonIdent(name, email, when, tz);
+ }
return null;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java
index 826ea518e4..2aa64df46f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java
@@ -146,6 +146,8 @@ public class RebaseResult {
};
/**
+ * Whether the rebase was successful
+ *
* @return whether the status indicates a successful result
*/
public abstract boolean isSuccessful();
@@ -194,6 +196,7 @@ public class RebaseResult {
* Create <code>RebaseResult</code>
*
* @param status
+ * the overall rebase status
* @param commit
* current commit
* @return the RebaseResult
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
index dead2749b7..a149649004 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
@@ -68,7 +68,7 @@ public class ReflogCommand extends GitCommand<Collection<ReflogEntry>> {
checkCallable();
try {
- ReflogReader reader = repo.getReflogReader(ref);
+ ReflogReader reader = repo.getRefDatabase().getReflogReader(ref);
if (reader == null)
throw new RefNotFoundException(MessageFormat.format(
JGitText.get().refNotResolved, ref));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java
index 553fc2e7a0..ad553f07a9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java
@@ -49,18 +49,6 @@ public class RemoteRemoveCommand extends GitCommand<RemoteConfig> {
/**
* The name of the remote to remove.
*
- * @param name
- * a remote name
- * @deprecated use {@link #setRemoteName} instead
- */
- @Deprecated
- public void setName(String name) {
- this.remoteName = name;
- }
-
- /**
- * The name of the remote to remove.
- *
* @param remoteName
* a remote name
* @return {@code this}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java
index e3d01861fc..68ddce361f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java
@@ -71,18 +71,6 @@ public class RemoteSetUrlCommand extends GitCommand<RemoteConfig> {
/**
* The name of the remote to change the URL for.
*
- * @param name
- * a remote name
- * @deprecated use {@link #setRemoteName} instead
- */
- @Deprecated
- public void setName(String name) {
- this.remoteName = name;
- }
-
- /**
- * The name of the remote to change the URL for.
- *
* @param remoteName
* a remote remoteName
* @return {@code this}
@@ -96,18 +84,6 @@ public class RemoteSetUrlCommand extends GitCommand<RemoteConfig> {
/**
* The new URL for the remote.
*
- * @param uri
- * an URL for the remote
- * @deprecated use {@link #setRemoteUri} instead
- */
- @Deprecated
- public void setUri(URIish uri) {
- this.remoteUri = uri;
- }
-
- /**
- * The new URL for the remote.
- *
* @param remoteUri
* an URL for the remote
* @return {@code this}
@@ -121,23 +97,6 @@ public class RemoteSetUrlCommand extends GitCommand<RemoteConfig> {
/**
* Whether to change the push URL of the remote instead of the fetch URL.
*
- * @param push
- * <code>true</code> to set the push url, <code>false</code> to
- * set the fetch url
- * @deprecated use {@link #setUriType} instead
- */
- @Deprecated
- public void setPush(boolean push) {
- if (push) {
- setUriType(UriType.PUSH);
- } else {
- setUriType(UriType.FETCH);
- }
- }
-
- /**
- * Whether to change the push URL of the remote instead of the fetch URL.
- *
* @param type
* the <code>UriType</code> value to set
* @return {@code this}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java
index f4b60adedb..c65cbf160f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoveNoteCommand.java
@@ -47,7 +47,6 @@ public class RemoveNoteCommand extends GitCommand<Note> {
super(repo);
}
- /** {@inheritDoc} */
@Override
public Note call() throws GitAPIException {
checkCallable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java
index 922cb531de..029f9d02d6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java
@@ -55,7 +55,6 @@ public class RenameBranchCommand extends GitCommand<Ref> {
super(repo);
}
- /** {@inheritDoc} */
@Override
public Ref call() throws GitAPIException, RefNotFoundException, InvalidRefNameException,
RefAlreadyExistsException, DetachedHeadException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
index 87b4acb14c..47145a0563 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
@@ -11,8 +11,8 @@ package org.eclipse.jgit.api;
import java.io.IOException;
import java.text.MessageFormat;
+import java.util.ArrayList;
import java.util.Collection;
-import java.util.LinkedList;
import org.eclipse.jgit.api.errors.CheckoutConflictException;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -90,7 +90,7 @@ public class ResetCommand extends GitCommand<Ref> {
private ResetType mode;
- private Collection<String> filepaths = new LinkedList<>();
+ private Collection<String> filepaths = new ArrayList<>();
private boolean isReflogDisabled;
@@ -436,7 +436,6 @@ public class ResetCommand extends GitCommand<Ref> {
repo.writeMergeCommitMsg(null);
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
index 513f579b67..6643c83662 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> and others
+ * Copyright (C) 2010, 2024 Christian Halstrick <christian.halstrick@sap.com> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -13,7 +13,7 @@ import static org.eclipse.jgit.lib.Constants.OBJECT_ID_ABBREV_STRING_LENGTH;
import java.io.IOException;
import java.text.MessageFormat;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -58,11 +58,13 @@ import org.eclipse.jgit.treewalk.FileTreeIterator;
* >Git documentation about revert</a>
*/
public class RevertCommand extends GitCommand<RevCommit> {
- private List<Ref> commits = new LinkedList<>();
+ private List<Ref> commits = new ArrayList<>();
private String ourCommitName = null;
- private List<Ref> revertedRefs = new LinkedList<>();
+ private boolean insertChangeId;
+
+ private List<Ref> revertedRefs = new ArrayList<>();
private MergeResult failingResult;
@@ -132,7 +134,7 @@ public class RevertCommand extends GitCommand<RevCommit> {
String ourName = calculateOurName(headRef);
String revertName = srcCommit.getId()
- .abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH).name() + " " //$NON-NLS-1$
+ .abbreviate(OBJECT_ID_ABBREV_STRING_LENGTH).name() + ' '
+ srcCommit.getShortMessage();
ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo);
@@ -141,8 +143,8 @@ public class RevertCommand extends GitCommand<RevCommit> {
merger.setCommitNames(new String[] {
"BASE", ourName, revertName }); //$NON-NLS-1$
- String shortMessage = "Revert \"" + srcCommit.getShortMessage() //$NON-NLS-1$
- + "\""; //$NON-NLS-1$
+ String shortMessage = "Revert \"" //$NON-NLS-1$
+ + srcCommit.getFirstMessageLine() + '"';
String newMessage = shortMessage + "\n\n" //$NON-NLS-1$
+ "This reverts commit " + srcCommit.getId().getName() //$NON-NLS-1$
+ ".\n"; //$NON-NLS-1$
@@ -162,6 +164,7 @@ public class RevertCommand extends GitCommand<RevCommit> {
dco.checkout();
try (Git git = new Git(getRepository())) {
newHead = git.commit().setMessage(newMessage)
+ .setInsertChangeId(insertChangeId)
.setReflogComment("revert: " + shortMessage) //$NON-NLS-1$
.call();
}
@@ -327,4 +330,18 @@ public class RevertCommand extends GitCommand<RevCommit> {
this.monitor = monitor;
return this;
}
+
+ /**
+ * Defines whether to add a Gerrit change ID to each revert commit message.
+ *
+ * @param insertChangeId
+ * whether to insert a change ID
+ * @return {@code this}
+ * @since 6.8
+ */
+ public RevertCommand setInsertChangeId(boolean insertChangeId) {
+ this.insertChangeId = insertChangeId;
+ return this;
+ }
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java
index 656f36a81a..7459e7298f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java
@@ -13,7 +13,6 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.LinkedList;
import java.util.List;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -71,7 +70,7 @@ public class RmCommand extends GitCommand<DirCache> {
*/
public RmCommand(Repository repo) {
super(repo);
- filepatterns = new LinkedList<>();
+ filepatterns = new ArrayList<>();
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java
index f64cb6b831..7bb9de0ef4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ShowNoteCommand.java
@@ -44,7 +44,6 @@ public class ShowNoteCommand extends GitCommand<Note> {
super(repo);
}
- /** {@inheritDoc} */
@Override
public Note call() throws GitAPIException {
checkCallable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
index 17036a9cd3..b0b715e06b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012, 2021 GitHub Inc. and others
+ * Copyright (C) 2012, 2023 GitHub Inc. and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -23,6 +23,7 @@ import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.StashApplyFailureException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
+import org.eclipse.jgit.dircache.Checkout;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheCheckout;
@@ -48,7 +49,6 @@ import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.treewalk.WorkingTreeOptions;
/**
* Command class to apply a stashed commit.
@@ -263,18 +263,6 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
/**
* Whether to restore the index state
*
- * @param applyIndex
- * true (default) if the command should restore the index state
- * @deprecated use {@link #setRestoreIndex} instead
- */
- @Deprecated
- public void setApplyIndex(boolean applyIndex) {
- this.restoreIndex = applyIndex;
- }
-
- /**
- * Whether to restore the index state
- *
* @param restoreIndex
* true (default) if the command should restore the index state
* @return {@code this}
@@ -319,19 +307,6 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
/**
* Whether the command should restore untracked files
*
- * @param applyUntracked
- * true (default) if the command should restore untracked files
- * @since 3.4
- * @deprecated use {@link #setRestoreUntracked} instead
- */
- @Deprecated
- public void setApplyUntracked(boolean applyUntracked) {
- this.restoreUntracked = applyUntracked;
- }
-
- /**
- * Whether the command should restore untracked files
- *
* @param restoreUntracked
* true (default) if the command should restore untracked files
* @return {@code this}
@@ -383,8 +358,7 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
private void resetUntracked(RevTree tree) throws CheckoutConflictException,
IOException {
Set<String> actuallyModifiedPaths = new HashSet<>();
- WorkingTreeOptions options = repo.getConfig()
- .get(WorkingTreeOptions.KEY);
+ Checkout checkout = new Checkout(repo).setRecursiveDeletion(true);
// TODO maybe NameConflictTreeWalk ?
try (TreeWalk walk = new TreeWalk(repo)) {
walk.addTree(tree);
@@ -408,17 +382,17 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
FileTreeIterator fIter = walk
.getTree(1, FileTreeIterator.class);
+ String gitPath = entry.getPathString();
if (fIter != null) {
if (fIter.isModified(entry, true, reader)) {
// file exists and is dirty
- throw new CheckoutConflictException(
- entry.getPathString());
+ throw new CheckoutConflictException(gitPath);
}
}
- checkoutPath(entry, reader, options,
+ checkoutPath(entry, gitPath, reader, checkout,
new CheckoutMetadata(eolStreamType, null));
- actuallyModifiedPaths.add(entry.getPathString());
+ actuallyModifiedPaths.add(gitPath);
}
} finally {
if (!actuallyModifiedPaths.isEmpty()) {
@@ -428,11 +402,11 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
}
}
- private void checkoutPath(DirCacheEntry entry, ObjectReader reader,
- WorkingTreeOptions options, CheckoutMetadata checkoutMetadata) {
+ private void checkoutPath(DirCacheEntry entry, String gitPath,
+ ObjectReader reader,
+ Checkout checkout, CheckoutMetadata checkoutMetadata) {
try {
- DirCacheCheckout.checkoutEntry(repo, entry, reader, true,
- checkoutMetadata, options);
+ checkout.checkout(entry, checkoutMetadata, reader, gitPath);
} catch (IOException e) {
throw new JGitInternalException(MessageFormat.format(
JGitText.get().checkoutConflictWithFile,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
index 23fbe0197f..2dba0ef0f2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
@@ -165,7 +165,8 @@ public class StashDropCommand extends GitCommand<ObjectId> {
List<ReflogEntry> entries;
try {
- ReflogReader reader = repo.getReflogReader(R_STASH);
+ ReflogReader reader = repo.getRefDatabase()
+ .getReflogReader(R_STASH);
if (reader == null) {
throw new RefNotFoundException(MessageFormat
.format(JGitText.get().refNotResolved, stashRef));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java
index 8171c4c3ae..828a3020f8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashListCommand.java
@@ -43,7 +43,6 @@ public class StashListCommand extends GitCommand<Collection<RevCommit>> {
super(repo);
}
- /** {@inheritDoc} */
@Override
public Collection<RevCommit> call() throws GitAPIException,
InvalidRefNameException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java
index eab389460a..cdd078ea25 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java
@@ -10,7 +10,7 @@
package org.eclipse.jgit.api;
import java.io.IOException;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -83,7 +83,7 @@ public class StatusCommand extends GitCommand<Status> {
*/
public StatusCommand addPath(String path) {
if (paths == null)
- paths = new LinkedList<>();
+ paths = new ArrayList<>();
paths.add(path);
return this;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
index 606d567393..5105dfc2e5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java
@@ -66,6 +66,7 @@ public class SubmoduleAddCommand extends
* Set the submodule name
*
* @param name
+ * name of the submodule
* @return this command
* @since 5.1
*/
@@ -117,6 +118,7 @@ public class SubmoduleAddCommand extends
*
* @return true if submodule exists in index, false otherwise
* @throws java.io.IOException
+ * if an IO error occurred
*/
protected boolean submoduleExists() throws IOException {
TreeFilter filter = PathFilter.create(path);
@@ -174,8 +176,9 @@ public class SubmoduleAddCommand extends
CloneCommand clone = Git.cloneRepository();
configure(clone);
clone.setDirectory(moduleDirectory);
- clone.setGitDir(new File(new File(repo.getDirectory(),
- Constants.MODULES), path));
+ clone.setGitDir(new File(
+ new File(repo.getCommonDirectory(), Constants.MODULES), path));
+ clone.setRelativePaths(true);
clone.setURI(resolvedUri);
if (monitor != null)
clone.setProgressMonitor(monitor);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java
index f4b8ac2e07..0aa1515334 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitCommand.java
@@ -61,6 +61,7 @@ public class SubmoduleDeinitCommand
* Constructor of SubmoduleDeinitCommand
*
* @param repo
+ * repository this command works on
*/
public SubmoduleDeinitCommand(Repository repo) {
super(repo);
@@ -69,7 +70,6 @@ public class SubmoduleDeinitCommand
/**
* {@inheritDoc}
- * <p>
*
* @return the set of repositories successfully deinitialized.
* @throws NoSuchSubmoduleException
@@ -135,6 +135,7 @@ public class SubmoduleDeinitCommand
* @param path
* the path to clean
* @throws IOException
+ * if an IO error occurred
*/
private void deinit(String path) throws IOException {
File dir = new File(repo.getWorkTree(), path);
@@ -157,10 +158,14 @@ public class SubmoduleDeinitCommand
* the parent repo's index or HEAD.
*
* @param revWalk
+ * used to walk commit graph
* @param path
+ * path of the submodule
* @return status of the command
* @throws GitAPIException
+ * if JGit API failed
* @throws IOException
+ * if an IO error occurred
*/
private SubmoduleDeinitStatus checkDirty(RevWalk revWalk, String path)
throws GitAPIException, IOException {
@@ -216,6 +221,7 @@ public class SubmoduleDeinitCommand
* @return {@code true} if path exists and is a submodule in index,
* {@code false} otherwise
* @throws IOException
+ * if an IO error occurred
*/
private boolean submoduleExists(String path) throws IOException {
TreeFilter filter = PathFilter.create(path);
@@ -241,6 +247,7 @@ public class SubmoduleDeinitCommand
* else it will refuse to do so.
*
* @param force
+ * execute the command forcefully if there are local modifications
* @return {@code this}
*/
public SubmoduleDeinitCommand setForce(boolean force) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java
index 8129be4a71..b7d7b66333 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleDeinitResult.java
@@ -25,6 +25,7 @@ public class SubmoduleDeinitResult {
* @param path
* path of the submodule
* @param status
+ * effect of a SubmoduleDeinitCommand's execution
*/
public SubmoduleDeinitResult(String path,
SubmoduleDeinitCommand.SubmoduleDeinitStatus status) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java
index bdceabad37..03b4a000ad 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java
@@ -61,7 +61,6 @@ public class SubmoduleInitCommand extends GitCommand<Collection<String>> {
return this;
}
- /** {@inheritDoc} */
@Override
public Collection<String> call() throws GitAPIException {
checkCallable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java
index 196ef7b2a9..d5bc0dda98 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java
@@ -61,7 +61,6 @@ public class SubmoduleStatusCommand extends
return this;
}
- /** {@inheritDoc} */
@Override
public Map<String, SubmoduleStatus> call() throws GitAPIException {
checkCallable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
index b319a1b478..4f3e8512c3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
@@ -70,6 +70,7 @@ public class SubmoduleSyncCommand extends GitCommand<Map<String, String>> {
* a {@link org.eclipse.jgit.lib.Repository} object.
* @return shortened branch name, null on failures
* @throws java.io.IOException
+ * if an IO error occurred
*/
protected String getHeadBranch(Repository subRepo) throws IOException {
Ref head = subRepo.exactRef(Constants.HEAD);
@@ -79,7 +80,6 @@ public class SubmoduleSyncCommand extends GitCommand<Map<String, String>> {
return null;
}
- /** {@inheritDoc} */
@Override
public Map<String, String> call() throws GitAPIException {
checkCallable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
index df73164161..5e4b2ee0b7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
@@ -28,6 +28,7 @@ import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.internal.storage.file.LockFile;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -39,6 +40,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.submodule.SubmoduleWalk;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
+import org.eclipse.jgit.util.FileUtils;
/**
* A class used to execute a submodule update command.
@@ -62,6 +64,8 @@ public class SubmoduleUpdateCommand extends
private boolean fetch = false;
+ private boolean clonedRestored;
+
/**
* <p>
* Constructor for SubmoduleUpdateCommand.
@@ -116,25 +120,77 @@ public class SubmoduleUpdateCommand extends
return this;
}
+ private static boolean submoduleExists(File gitDir) {
+ if (gitDir != null && gitDir.isDirectory()) {
+ File[] files = gitDir.listFiles();
+ return files != null && files.length != 0;
+ }
+ return false;
+ }
+
+ private static void restoreSubmodule(File gitDir, File workingTree)
+ throws IOException {
+ LockFile dotGitLock = new LockFile(
+ new File(workingTree, Constants.DOT_GIT));
+ if (dotGitLock.lock()) {
+ String content = Constants.GITDIR
+ + getRelativePath(gitDir, workingTree);
+ dotGitLock.write(Constants.encode(content));
+ dotGitLock.commit();
+ }
+ }
+
+ private static String getRelativePath(File gitDir, File workingTree) {
+ File relPath;
+ try {
+ relPath = workingTree.toPath().relativize(gitDir.toPath())
+ .toFile();
+ } catch (IllegalArgumentException e) {
+ relPath = gitDir;
+ }
+ return FileUtils.pathToString(relPath);
+ }
+
+ private String determineUpdateMode(String mode) {
+ if (clonedRestored) {
+ return ConfigConstants.CONFIG_KEY_CHECKOUT;
+ }
+ return mode;
+ }
+
private Repository getOrCloneSubmodule(SubmoduleWalk generator, String url)
throws IOException, GitAPIException {
Repository repository = generator.getRepository();
+ boolean restored = false;
+ boolean cloned = false;
if (repository == null) {
- if (callback != null) {
- callback.cloningSubmodule(generator.getPath());
- }
- CloneCommand clone = Git.cloneRepository();
- configure(clone);
- clone.setURI(url);
- clone.setDirectory(generator.getDirectory());
- clone.setGitDir(
- new File(new File(repo.getDirectory(), Constants.MODULES),
- generator.getPath()));
- if (monitor != null) {
- clone.setProgressMonitor(monitor);
+ File gitDir = new File(
+ new File(repo.getCommonDirectory(), Constants.MODULES),
+ generator.getPath());
+ if (submoduleExists(gitDir)) {
+ restoreSubmodule(gitDir, generator.getDirectory());
+ restored = true;
+ clonedRestored = true;
+ repository = generator.getRepository();
+ } else {
+ if (callback != null) {
+ callback.cloningSubmodule(generator.getPath());
+ }
+ CloneCommand clone = Git.cloneRepository();
+ configure(clone);
+ clone.setURI(url);
+ clone.setDirectory(generator.getDirectory());
+ clone.setGitDir(gitDir);
+ clone.setRelativePaths(true);
+ if (monitor != null) {
+ clone.setProgressMonitor(monitor);
+ }
+ repository = clone.call().getRepository();
+ cloned = true;
+ clonedRestored = true;
}
- repository = clone.call().getRepository();
- } else if (this.fetch) {
+ }
+ if ((this.fetch || restored) && !cloned) {
if (fetchCallback != null) {
fetchCallback.fetchingSubmodule(generator.getPath());
}
@@ -171,15 +227,17 @@ public class SubmoduleUpdateCommand extends
continue;
// Skip submodules not registered in parent repository's config
String url = generator.getConfigUrl();
- if (url == null)
+ if (url == null) {
continue;
-
+ }
+ clonedRestored = false;
try (Repository submoduleRepo = getOrCloneSubmodule(generator,
url); RevWalk walk = new RevWalk(submoduleRepo)) {
RevCommit commit = walk
.parseCommit(generator.getObjectId());
- String update = generator.getConfigUpdate();
+ String update = determineUpdateMode(
+ generator.getConfigUpdate());
if (ConfigConstants.CONFIG_KEY_MERGE.equals(update)) {
MergeCommand merge = new MergeCommand(submoduleRepo);
merge.include(commit);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java
index 58c18b38d1..cc8589fa1c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/TagCommand.java
@@ -18,14 +18,11 @@ import org.eclipse.jgit.api.errors.InvalidTagNameException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
-import org.eclipse.jgit.api.errors.ServiceUnavailableException;
import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.GpgConfig;
import org.eclipse.jgit.lib.GpgConfig.GpgFormat;
-import org.eclipse.jgit.lib.GpgObjectSigner;
-import org.eclipse.jgit.lib.GpgSigner;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
@@ -33,7 +30,8 @@ import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.RepositoryState;
+import org.eclipse.jgit.lib.Signer;
+import org.eclipse.jgit.lib.Signers;
import org.eclipse.jgit.lib.TagBuilder;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -51,13 +49,11 @@ import org.eclipse.jgit.transport.CredentialsProvider;
* </pre>
* <p>
*
- * <p>
* Create a new unannotated tag for the current commit:
*
* <pre>
* git.tag().setName(&quot;v1.0&quot;).setAnnotated(false).call();
* </pre>
- * <p>
*
* @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-tag.html"
* >Git documentation about Tag</a>
@@ -82,7 +78,7 @@ public class TagCommand extends GitCommand<Ref> {
private GpgConfig gpgConfig;
- private GpgObjectSigner gpgSigner;
+ private Signer signer;
private CredentialsProvider credentialsProvider;
@@ -110,9 +106,7 @@ public class TagCommand extends GitCommand<Ref> {
public Ref call() throws GitAPIException, ConcurrentRefUpdateException,
InvalidTagNameException, NoHeadException {
checkCallable();
-
- RepositoryState state = repo.getRepositoryState();
- processOptions(state);
+ processOptions();
try (RevWalk revWalk = new RevWalk(repo)) {
// if no id is set, we should attempt to use HEAD
@@ -138,9 +132,9 @@ public class TagCommand extends GitCommand<Ref> {
newTag.setTagger(tagger);
newTag.setObjectId(id);
- if (gpgSigner != null) {
- gpgSigner.signObject(newTag, signingKey, tagger,
- credentialsProvider, gpgConfig);
+ if (signer != null) {
+ signer.signObject(repo, gpgConfig, newTag, tagger, signingKey,
+ credentialsProvider);
}
// write the tag object
@@ -199,20 +193,14 @@ public class TagCommand extends GitCommand<Ref> {
* Sets default values for not explicitly specified options. Then validates
* that all required data has been provided.
*
- * @param state
- * the state of the repository we are working on
- *
* @throws InvalidTagNameException
* if the tag name is null or invalid
- * @throws ServiceUnavailableException
- * if the tag should be signed but no signer can be found
* @throws UnsupportedSigningFormatException
* if the tag should be signed but {@code gpg.format} is not
* {@link GpgFormat#OPENPGP}
*/
- private void processOptions(RepositoryState state)
- throws InvalidTagNameException, ServiceUnavailableException,
- UnsupportedSigningFormatException {
+ private void processOptions()
+ throws InvalidTagNameException, UnsupportedSigningFormatException {
if (name == null
|| !Repository.isValidRefName(Constants.R_TAGS + name)) {
throw new InvalidTagNameException(
@@ -238,16 +226,15 @@ public class TagCommand extends GitCommand<Ref> {
doSign = gpgConfig.isSignAnnotated();
}
if (doSign) {
- if (signingKey == null) {
- signingKey = gpgConfig.getSigningKey();
- }
- if (gpgSigner == null) {
- GpgSigner signer = GpgSigner.getDefault();
- if (!(signer instanceof GpgObjectSigner)) {
- throw new ServiceUnavailableException(
- JGitText.get().signingServiceUnavailable);
+ if (signer == null) {
+ signer = Signers.get(gpgConfig.getKeyFormat());
+ if (signer == null) {
+ throw new UnsupportedSigningFormatException(
+ MessageFormat.format(
+ JGitText.get().signatureTypeUnknown,
+ gpgConfig.getKeyFormat()
+ .toConfigValue()));
}
- gpgSigner = (GpgObjectSigner) signer;
}
// The message of a signed tag must end in a newline because
// the signature will be appended.
@@ -334,22 +321,22 @@ public class TagCommand extends GitCommand<Ref> {
}
/**
- * Sets the {@link GpgSigner} to use if the commit is to be signed.
+ * Sets the {@link Signer} to use if the commit is to be signed.
*
* @param signer
* to use; if {@code null}, the default signer will be used
* @return {@code this}
- * @since 5.11
+ * @since 7.0
*/
- public TagCommand setGpgSigner(GpgObjectSigner signer) {
+ public TagCommand setSigner(Signer signer) {
checkCallable();
- this.gpgSigner = signer;
+ this.signer = signer;
return this;
}
/**
* Sets an external {@link GpgConfig} to use. Whether it will be used is at
- * the discretion of the {@link #setGpgSigner(GpgObjectSigner)}.
+ * the discretion of the {@link #setSigner(Signer)}.
*
* @param config
* to set; if {@code null}, the config will be loaded from the
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java
index 1af880d792..30f1bc9cc6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java
@@ -22,7 +22,9 @@ import org.eclipse.jgit.transport.Transport;
* {@link org.eclipse.jgit.api.TransportConfigCallback}.
*
* @param <C>
+ * concrete type of this {@code GitCommand}
* @param <T>
+ * the return type of the {@code GitCommand}'s {@code call()} method
*/
public abstract class TransportCommand<C extends GitCommand, T> extends
GitCommand<T> {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java
index 21cddf75b7..f5f4b06e45 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/VerificationResult.java
@@ -9,7 +9,7 @@
*/
package org.eclipse.jgit.api;
-import org.eclipse.jgit.lib.GpgSignatureVerifier;
+import org.eclipse.jgit.lib.SignatureVerifier;
import org.eclipse.jgit.revwalk.RevObject;
/**
@@ -34,8 +34,9 @@ public interface VerificationResult {
* Retrieves the signature verification result.
*
* @return the result, or {@code null} if none was computed
+ * @since 7.0
*/
- GpgSignatureVerifier.SignatureVerification getVerification();
+ SignatureVerifier.SignatureVerification getVerification();
/**
* Retrieves the git object of which the signature was verified.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java
index 6a2a44ea2d..487ff04323 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/VerifySignatureCommand.java
@@ -25,11 +25,10 @@ import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.GpgConfig;
-import org.eclipse.jgit.lib.GpgSignatureVerifier;
-import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification;
-import org.eclipse.jgit.lib.GpgSignatureVerifierFactory;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SignatureVerifier.SignatureVerification;
+import org.eclipse.jgit.lib.SignatureVerifiers;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -65,12 +64,8 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR
private VerifyMode mode = VerifyMode.ANY;
- private GpgSignatureVerifier verifier;
-
private GpgConfig config;
- private boolean ownVerifier;
-
/**
* Creates a new {@link VerifySignatureCommand} for the given {@link Repository}.
*
@@ -140,22 +135,7 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR
}
/**
- * Sets the {@link GpgSignatureVerifier} to use.
- *
- * @param verifier
- * the {@link GpgSignatureVerifier} to use, or {@code null} to
- * use the default verifier
- * @return {@code this}
- */
- public VerifySignatureCommand setVerifier(GpgSignatureVerifier verifier) {
- checkCallable();
- this.verifier = verifier;
- return this;
- }
-
- /**
- * Sets an external {@link GpgConfig} to use. Whether it will be used it at
- * the discretion of the {@link #setVerifier(GpgSignatureVerifier)}.
+ * Sets an external {@link GpgConfig} to use.
*
* @param config
* to set; if {@code null}, the config will be loaded from the
@@ -170,16 +150,6 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR
}
/**
- * Retrieves the currently set {@link GpgSignatureVerifier}. Can be used
- * after a successful {@link #call()} to get the verifier that was used.
- *
- * @return the {@link GpgSignatureVerifier}
- */
- public GpgSignatureVerifier getVerifier() {
- return verifier;
- }
-
- /**
* {@link Repository#resolve(String) Resolves} all names added to the
* command to git objects and verifies their signature. Non-existing objects
* are ignored.
@@ -193,9 +163,6 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR
*
* @return a map of the given names to the corresponding
* {@link VerificationResult}, excluding ignored or skipped objects.
- * @throws ServiceUnavailableException
- * if no {@link GpgSignatureVerifier} was set and no
- * {@link GpgSignatureVerifierFactory} is available
* @throws WrongObjectTypeException
* if a name resolves to an object of a type not allowed by the
* {@link #setMode(VerifyMode)} mode
@@ -207,16 +174,6 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR
checkCallable();
setCallable(false);
Map<String, VerificationResult> result = new HashMap<>();
- if (verifier == null) {
- GpgSignatureVerifierFactory factory = GpgSignatureVerifierFactory
- .getDefault();
- if (factory == null) {
- throw new ServiceUnavailableException(
- JGitText.get().signatureVerificationUnavailable);
- }
- verifier = factory.getVerifier();
- ownVerifier = true;
- }
if (config == null) {
config = new GpgConfig(repo.getConfig());
}
@@ -239,10 +196,6 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR
} catch (IOException e) {
throw new JGitInternalException(
JGitText.get().signatureVerificationError, e);
- } finally {
- if (ownVerifier) {
- verifier.clear();
- }
}
return result;
}
@@ -258,8 +211,8 @@ public class VerifySignatureCommand extends GitCommand<Map<String, VerificationR
}
if (type == Constants.OBJ_COMMIT || type == Constants.OBJ_TAG) {
try {
- GpgSignatureVerifier.SignatureVerification verification = verifier
- .verifySignature(object, config);
+ SignatureVerification verification = SignatureVerifiers
+ .verify(repo, config, object);
if (verification == null) {
// Not signed
return null;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java
index 3b71373b6e..5538711192 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CheckoutConflictException.java
@@ -37,7 +37,7 @@
*/
package org.eclipse.jgit.api.errors;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -94,11 +94,12 @@ public class CheckoutConflictException extends GitAPIException {
* Adds a new conflicting path
*
* @param conflictingPath
+ * the new conflicting path
* @return {@code this}
*/
CheckoutConflictException addConflictingPath(String conflictingPath) {
if (conflictingPaths == null)
- conflictingPaths = new LinkedList<>();
+ conflictingPaths = new ArrayList<>();
conflictingPaths.add(conflictingPath);
return this;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
index 470a92eaf5..9c4d8700a2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
@@ -9,6 +9,8 @@
*/
package org.eclipse.jgit.attributes;
+import org.eclipse.jgit.annotations.Nullable;
+
/**
* Represents an attribute.
* <p>
@@ -97,7 +99,6 @@ public final class Attribute {
this(key, State.CUSTOM, value);
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object obj) {
if (this == obj)
@@ -140,11 +141,11 @@ public final class Attribute {
*
* @return the attribute value (may be <code>null</code>)
*/
+ @Nullable
public String getValue() {
return value;
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
final int prime = 31;
@@ -155,7 +156,6 @@ public final class Attribute {
return result;
}
- /** {@inheritDoc} */
@Override
public String toString() {
switch (state) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java
index 125ee5961a..08bc1da61e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java
@@ -231,7 +231,6 @@ public final class Attributes {
return true;
}
- /** {@inheritDoc} */
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
@@ -246,13 +245,11 @@ public final class Attributes {
return buf.toString();
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
return map.hashCode();
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object obj) {
if (this == obj)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java
index 7ec78597fa..d8857f5192 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java
@@ -69,6 +69,7 @@ public class AttributesHandler {
* @param treeWalk
* a {@link org.eclipse.jgit.treewalk.TreeWalk}
* @throws java.io.IOException
+ * if an IO error occurred
* @deprecated since 6.1, use {@link #AttributesHandler(TreeWalk, Supplier)}
* instead
*/
@@ -87,6 +88,7 @@ public class AttributesHandler {
* @param attributesTree
* the tree to read .gitattributes from
* @throws java.io.IOException
+ * if an IO error occurred
* @since 6.1
*/
public AttributesHandler(TreeWalk treeWalk,
@@ -128,6 +130,7 @@ public class AttributesHandler {
* current path represented by the
* {@link org.eclipse.jgit.treewalk.TreeWalk}
* @throws java.io.IOException
+ * if an IO error occurred
*/
public Attributes getAttributes() throws IOException {
String entryPath = treeWalk.getPathString();
@@ -206,12 +209,16 @@ public class AttributesHandler {
* @param isDirectory
* true if the target item is a directory.
* @param workingTreeIterator
+ * the working tree iterator
* @param dirCacheIterator
+ * the dircache iterator
* @param otherTree
+ * another tree
* @param result
* that will hold the attributes matching this entry path. This
* method will NOT override any existing entry in attributes.
* @throws IOException
+ * if an IO error occurred
*/
private void mergePerDirectoryEntryAttributes(String entryPath,
int nameRoot, boolean isDirectory,
@@ -357,9 +364,13 @@ public class AttributesHandler {
* </p>
*
* @param treeWalk
+ * used to walk trees
* @param workingTreeIterator
+ * used to walk the working tree
* @param dirCacheIterator
+ * used to walk the dircache
* @param otherTree
+ * another tree
* @return a {@link AttributesNode} of the current entry,
* {@link NullPointerException} otherwise.
* @throws IOException
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
index 73fd587a14..2039191b8c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
@@ -187,7 +187,6 @@ public class AttributesRule {
return match;
}
- /** {@inheritDoc} */
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandRegistry.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandRegistry.java
index 1c9e9d7f71..bbbb437fd2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandRegistry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/FilterCommandRegistry.java
@@ -41,8 +41,8 @@ public class FilterCommandRegistry {
* the factory responsible for creating
* {@link org.eclipse.jgit.attributes.FilterCommand}s for the
* specified name
- * @return the previous factory associated with <tt>commandName</tt>, or
- * <tt>null</tt> if there was no mapping for <tt>commandName</tt>
+ * @return the previous factory associated with {@code commandName}, or
+ * {@code null} if there was no mapping for {@code commandName}
*/
public static FilterCommandFactory register(String filterCommandName,
FilterCommandFactory factory) {
@@ -55,8 +55,8 @@ public class FilterCommandRegistry {
*
* @param filterCommandName
* the FilterCommandFactory's filter command name
- * @return the previous factory associated with <tt>filterCommandName</tt>,
- * or <tt>null</tt> if there was no mapping for <tt>commandName</tt>
+ * @return the previous factory associated with {@code filterCommandName},
+ * or {@code null} if there was no mapping for {@code commandName}
*/
public static FilterCommandFactory unregister(String filterCommandName) {
return filterCommandRegistry.remove(filterCommandName);
@@ -69,7 +69,7 @@ public class FilterCommandRegistry {
*
* @param filterCommandName
* the name for which the registry should be checked
- * @return <code>true</code> if any factory was registered for the name
+ * @return {@code true} if any factory was registered for the name
*/
public static boolean isRegistered(String filterCommandName) {
return filterCommandRegistry.containsKey(filterCommandName);
@@ -107,6 +107,7 @@ public class FilterCommandRegistry {
* @return the command if a command could be created or <code>null</code> if
* there was no factory registered for that name
* @throws java.io.IOException
+ * if an IO error occurred
*/
public static FilterCommand createFilterCommand(String filterCommandName,
Repository db, InputStream in, OutputStream out)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
index 77967df2e5..979c8cef88 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
@@ -28,6 +28,8 @@ import org.eclipse.jgit.blame.Candidate.BlobCandidate;
import org.eclipse.jgit.blame.Candidate.HeadCandidate;
import org.eclipse.jgit.blame.Candidate.ReverseCandidate;
import org.eclipse.jgit.blame.ReverseWalk.ReverseCommit;
+import org.eclipse.jgit.blame.cache.BlameCache;
+import org.eclipse.jgit.blame.cache.CacheRegion;
import org.eclipse.jgit.diff.DiffAlgorithm;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
@@ -129,8 +131,19 @@ public class BlameGenerator implements AutoCloseable {
/** Blame is currently assigned to this source. */
private Candidate outCandidate;
+
private Region outRegion;
+ private final BlameCache blameCache;
+
+ /**
+ * Blame in reverse order needs the source lines, but we don't have them in
+ * the cache. We need to ignore the cache in that case.
+ */
+ private boolean useCache = true;
+
+ private final Stats stats = new Stats();
+
/**
* Create a blame generator for the repository and path (relative to
* repository)
@@ -142,6 +155,25 @@ public class BlameGenerator implements AutoCloseable {
* repository).
*/
public BlameGenerator(Repository repository, String path) {
+ this(repository, path, null);
+ }
+
+ /**
+ * Create a blame generator for the repository and path (relative to
+ * repository)
+ *
+ * @param repository
+ * repository to access revision data from.
+ * @param path
+ * initial path of the file to start scanning (relative to the
+ * repository).
+ * @param blameCache
+ * previously calculated blames. This generator will *not*
+ * populate it, just consume it.
+ * @since 7.2
+ */
+ public BlameGenerator(Repository repository, String path,
+ @Nullable BlameCache blameCache) {
this.repository = repository;
this.resultPath = PathFilter.create(path);
@@ -150,6 +182,7 @@ public class BlameGenerator implements AutoCloseable {
initRevPool(false);
remaining = -1;
+ this.blameCache = blameCache;
}
private void initRevPool(boolean reverse) {
@@ -159,10 +192,12 @@ public class BlameGenerator implements AutoCloseable {
if (revPool != null)
revPool.close();
- if (reverse)
+ if (reverse) {
+ useCache = false;
revPool = new ReverseWalk(getRepository());
- else
+ } else {
revPool = new RevWalk(getRepository());
+ }
SEEN = revPool.newFlag("SEEN"); //$NON-NLS-1$
reader = revPool.getObjectReader();
@@ -245,6 +280,31 @@ public class BlameGenerator implements AutoCloseable {
}
/**
+ * Stats about this generator
+ *
+ * @return the stats of this generator
+ * @since 7.2
+ */
+ public Stats getStats() {
+ return stats;
+ }
+
+ /**
+ * Enable/disable the use of cache (if present). Enabled by default.
+ * <p>
+ * If caller need source line numbers, the generator cannot use the cache
+ * (source lines are not there). Use this method to disable the cache in
+ * that case.
+ *
+ * @param useCache
+ * should this generator use the cache.
+ * @since 7.2
+ */
+ public void setUseCache(boolean useCache) {
+ this.useCache = useCache;
+ }
+
+ /**
* Push a candidate blob onto the generator's traversal stack.
* <p>
* Candidates should be pushed in history order from oldest-to-newest.
@@ -591,6 +651,7 @@ public class BlameGenerator implements AutoCloseable {
Candidate n = pop();
if (n == null)
return done();
+ stats.candidatesVisited += 1;
int pCnt = n.getParentCount();
if (pCnt == 1) {
@@ -605,7 +666,7 @@ public class BlameGenerator implements AutoCloseable {
// Do not generate a tip of a reverse. The region
// survives and should not appear to be deleted.
- } else /* if (pCnt == 0) */{
+ } else /* if (pCnt == 0) */ {
// Root commit, with at least one surviving region.
// Assign the remaining blame here.
return result(n);
@@ -695,6 +756,27 @@ public class BlameGenerator implements AutoCloseable {
}
}
+ @Nullable
+ private Candidate blameFromCache(Candidate n) throws IOException {
+ if (blameCache == null || !useCache) {
+ return null;
+ }
+
+ List<CacheRegion> cachedBlame = blameCache.get(repository,
+ n.sourceCommit, n.sourcePath.getPath());
+ if (cachedBlame == null) {
+ return null;
+ }
+ BlameRegionMerger rb = new BlameRegionMerger(repository, revPool,
+ cachedBlame);
+ Candidate fullyBlamed = rb.mergeCandidate(n);
+ if (fullyBlamed == null) {
+ return null;
+ }
+ stats.cacheHit = true;
+ return fullyBlamed;
+ }
+
private boolean processOne(Candidate n) throws IOException {
RevCommit parent = n.getParent(0);
if (parent == null)
@@ -717,12 +799,17 @@ public class BlameGenerator implements AutoCloseable {
if (0 == r.getOldId().prefixCompare(n.sourceBlob)) {
// A 100% rename without any content change can also
// skip directly to the parent.
+ Candidate cached = blameFromCache(n);
+ if (cached != null) {
+ return result(cached);
+ }
n.sourceCommit = parent;
n.sourcePath = PathFilter.create(r.getOldPath());
push(n);
return false;
}
+
Candidate next = n.create(getRepository(), parent,
PathFilter.create(r.getOldPath()));
next.sourceBlob = r.getOldId().toObjectId();
@@ -759,6 +846,11 @@ public class BlameGenerator implements AutoCloseable {
return false;
}
+ Candidate cached = blameFromCache(source);
+ if (cached != null) {
+ return result(cached);
+ }
+
parent.takeBlame(editList, source);
if (parent.regionList != null)
push(parent);
@@ -846,8 +938,8 @@ public class BlameGenerator implements AutoCloseable {
editList = new EditList(0);
} else {
p.loadText(reader);
- editList = diffAlgorithm.diff(textComparator,
- p.sourceText, n.sourceText);
+ editList = diffAlgorithm.diff(textComparator, p.sourceText,
+ n.sourceText);
}
if (editList.isEmpty()) {
@@ -981,6 +1073,10 @@ public class BlameGenerator implements AutoCloseable {
/**
* Get first line of the source data that has been blamed for the current
* region
+ * <p>
+ * This value is not reliable when the generator is reusing cached values.
+ * Cache doesn't keep the source lines, the returned value is based on the
+ * result and can be off if the region moved in previous commits.
*
* @return first line of the source data that has been blamed for the
* current region. This is line number of where the region was added
@@ -994,6 +1090,10 @@ public class BlameGenerator implements AutoCloseable {
/**
* Get one past the range of the source data that has been blamed for the
* current region
+ * <p>
+ * This value is not reliable when the generator is reusing cached values.
+ * Cache doesn't keep the source lines, the returned value is based on the
+ * result and can be off if the region moved in previous commits.
*
* @return one past the range of the source data that has been blamed for
* the current region. This is line number of where the region was
@@ -1124,4 +1224,39 @@ public class BlameGenerator implements AutoCloseable {
return ent.getChangeType() == ChangeType.RENAME
|| ent.getChangeType() == ChangeType.COPY;
}
+
+ /**
+ * Stats about the work done by the generator
+ *
+ * @since 7.2
+ */
+ public static class Stats {
+
+ /** Candidates taken from the queue */
+ private int candidatesVisited;
+
+ private boolean cacheHit;
+
+ /**
+ * Number of candidates taken from the queue
+ * <p>
+ * The generator could signal it's done without exhausting all
+ * candidates if there is no more remaining lines or the last visited
+ * candidate is found in the cache.
+ *
+ * @return number of candidates taken from the queue
+ */
+ public int getCandidatesVisited() {
+ return candidatesVisited;
+ }
+
+ /**
+ * The generator found a blamed version in the cache
+ *
+ * @return true if we used results from the cache
+ */
+ public boolean isCacheHit() {
+ return cacheHit;
+ }
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameRegionMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameRegionMerger.java
new file mode 100644
index 0000000000..67bc6fb789
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameRegionMerger.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2025, Google LLC.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.blame;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.blame.cache.CacheRegion;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+
+/**
+ * Translates an unblamed region into one or more blamed regions, using the
+ * fully blamed data from cache.
+ * <p>
+ * Blamed and unblamed regions are not symmetrical: An unblamed region is just a
+ * range of lines over the file. A blamed region is a Candidate (with the commit
+ * info) with a region inside (the range blamed).
+ */
+class BlameRegionMerger {
+ private final Repository repo;
+
+ private final List<CacheRegion> cachedRegions;
+
+ private final RevWalk rw;
+
+ BlameRegionMerger(Repository repo, RevWalk rw,
+ List<CacheRegion> cachedRegions) {
+ this.repo = repo;
+ List<CacheRegion> sorted = new ArrayList<>(cachedRegions);
+ Collections.sort(sorted);
+ this.cachedRegions = sorted;
+ this.rw = rw;
+ }
+
+ /**
+ * Return one or more candidates blaming all the regions of the "unblamed"
+ * incoming candidate.
+ *
+ * @param candidate
+ * a candidate with a list of unblamed regions
+ * @return A linked list of Candidates with their blamed regions, null if
+ * there was any error.
+ */
+ Candidate mergeCandidate(Candidate candidate) {
+ List<Candidate> newCandidates = new ArrayList<>();
+ Region r = candidate.regionList;
+ while (r != null) {
+ try {
+ newCandidates.addAll(mergeOneRegion(r));
+ } catch (IOException e) {
+ return null;
+ }
+ r = r.next;
+ }
+ return asLinkedCandidate(newCandidates);
+ }
+
+ // Visible for testing
+ List<Candidate> mergeOneRegion(Region region) throws IOException {
+ List<CacheRegion> overlaps = findOverlaps(region);
+ if (overlaps.isEmpty()) {
+ throw new IOException(
+ "Cached blame should cover all lines");
+ }
+ /*
+ * Cached regions cover the whole file. We find first which ones overlap
+ * with our unblamed region. Then we take the overlapping portions with
+ * the corresponding blame.
+ */
+ List<Candidate> candidates = new ArrayList<>();
+ for (CacheRegion overlap : overlaps) {
+ Region blamedRegions = intersectRegions(region, overlap);
+ Candidate c = new Candidate(repo, parse(overlap.getSourceCommit()),
+ PathFilter.create(overlap.getSourcePath()));
+ c.regionList = blamedRegions;
+ candidates.add(c);
+ }
+ return candidates;
+ }
+
+ // Visible for testing
+ List<CacheRegion> findOverlaps(Region unblamed) {
+ int unblamedStart = unblamed.sourceStart;
+ int unblamedEnd = unblamedStart + unblamed.length;
+ List<CacheRegion> overlapping = new ArrayList<>();
+ for (CacheRegion blamed : cachedRegions) {
+ // End is not included
+ if (blamed.getEnd() <= unblamedStart) {
+ // Blamed region is completely before
+ continue;
+ }
+
+ if (blamed.getStart() >= unblamedEnd) {
+ // Blamed region is completely after
+ // Blamed regions are sorted by start position, nothing will
+ // match anymore
+ break;
+ }
+ overlapping.add(blamed);
+ }
+ return overlapping;
+ }
+
+ // Visible for testing
+ /**
+ * Calculate the intersection between a Region and a CacheRegion, adjusting
+ * the start if needed.
+ * <p>
+ * This should be called only if there is an overlap (filtering the cached
+ * regions with {@link #findOverlaps(Region)}), otherwise the result is
+ * meaningless.
+ *
+ * @param unblamed
+ * a region from the blame generator
+ * @param cached
+ * a cached region
+ * @return a new region with the intersection.
+ */
+ static Region intersectRegions(Region unblamed, CacheRegion cached) {
+ int blamedStart = Math.max(cached.getStart(), unblamed.sourceStart);
+ int blamedEnd = Math.min(cached.getEnd(),
+ unblamed.sourceStart + unblamed.length);
+ int length = blamedEnd - blamedStart;
+
+ // result start and source start should move together
+ int blameStartDelta = blamedStart - unblamed.sourceStart;
+ return new Region(unblamed.resultStart + blameStartDelta, blamedStart,
+ length);
+ }
+
+ // Tests can override this, so they don't need a real repo, commit and walk
+ protected RevCommit parse(ObjectId oid) throws IOException {
+ return rw.parseCommit(oid);
+ }
+
+ private static Candidate asLinkedCandidate(List<Candidate> c) {
+ Candidate head = c.get(0);
+ Candidate tail = head;
+ for (int i = 1; i < c.size(); i++) {
+ tail.queueNext = c.get(i);
+ tail = tail.queueNext;
+ }
+ return head;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java
index c854b37b84..5e2746cc7c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java
@@ -303,7 +303,6 @@ public class BlameResult {
}
}
- /** {@inheritDoc} */
@Override
public String toString() {
StringBuilder r = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/Candidate.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/Candidate.java
index ca5370e912..8e2aaecf94 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/blame/Candidate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/Candidate.java
@@ -12,6 +12,7 @@ package org.eclipse.jgit.blame;
import java.io.IOException;
import java.util.List;
+import java.util.Objects;
import org.eclipse.jgit.blame.ReverseWalk.ReverseCommit;
import org.eclipse.jgit.diff.Edit;
@@ -269,7 +270,7 @@ class Candidate {
}
boolean canMergeRegions(Candidate other) {
- return sourceCommit == other.sourceCommit
+ return Objects.equals(sourceCommit, other.sourceCommit)
&& sourcePath.getPath().equals(other.sourcePath.getPath());
}
@@ -304,7 +305,6 @@ class Candidate {
}
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/Region.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/Region.java
index 2236eecbfe..c481eb1927 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/blame/Region.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/Region.java
@@ -83,7 +83,6 @@ class Region {
return head;
}
- /** {@inheritDoc} */
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/ReverseWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/ReverseWalk.java
index e8f3f38adb..fafc4fb1ec 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/blame/ReverseWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/ReverseWalk.java
@@ -24,7 +24,6 @@ final class ReverseWalk extends RevWalk {
super(repo);
}
- /** {@inheritDoc} */
@Override
public ReverseCommit next() throws MissingObjectException,
IncorrectObjectTypeException, IOException {
@@ -36,7 +35,6 @@ final class ReverseWalk extends RevWalk {
return c;
}
- /** {@inheritDoc} */
@Override
protected RevCommit createCommit(AnyObjectId id) {
return new ReverseCommit(id);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/cache/BlameCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/cache/BlameCache.java
new file mode 100644
index 0000000000..d44fb5f62b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/cache/BlameCache.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2025, Google LLC.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.blame.cache;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Keeps the blame information for a path at certain commit.
+ * <p>
+ * If there is a result, it covers the whole file at that revision
+ *
+ * @since 7.2
+ */
+public interface BlameCache {
+ /**
+ * Gets the blame of a path at a given commit if available.
+ * <p>
+ * Since this cache is used in blame calculation, this get() method should
+ * only retrieve the cache value, and not re-trigger blame calculation. In
+ * other words, this acts as "getIfPresent", and not "computeIfAbsent".
+ *
+ * @param repo
+ * repository containing the commit
+ * @param commitId
+ * we are looking at the file in this revision
+ * @param path
+ * path a file in the repo
+ *
+ * @return the blame of a path at a given commit or null if not in cache
+ * @throws IOException
+ * error retrieving/parsing values from storage
+ */
+ List<CacheRegion> get(Repository repo, ObjectId commitId, String path)
+ throws IOException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/cache/CacheRegion.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/cache/CacheRegion.java
new file mode 100644
index 0000000000..cf3f978044
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/cache/CacheRegion.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2025, Google LLC.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.blame.cache;
+
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * Region of the blame of a file.
+ * <p>
+ * Usually all parameters are non-null, except when the Region was created
+ * to fill an unblamed gap (to cover for bugs in the calculation). In that
+ * case, path, commit and author will be null.
+ *
+ * @since 7.2
+ **/
+public class CacheRegion implements Comparable<CacheRegion> {
+ private final String sourcePath;
+
+ private final ObjectId sourceCommit;
+
+ private final int end;
+
+ private final int start;
+
+ /**
+ * A blamed portion of a file
+ *
+ * @param path
+ * location of the file
+ * @param commit
+ * commit that is modifying this region
+ * @param start
+ * first line of this region (inclusive)
+ * @param end
+ * last line of this region (non-inclusive!)
+ */
+ public CacheRegion(String path, ObjectId commit,
+ int start, int end) {
+ allOrNoneNull(path, commit);
+ this.sourcePath = path;
+ this.sourceCommit = commit;
+ this.start = start;
+ this.end = end;
+ }
+
+ /**
+ * First line of this region. Starting by 0, inclusive
+ *
+ * @return first line of this region.
+ */
+ public int getStart() {
+ return start;
+ }
+
+ /**
+ * One after last line in this region (or: last line non-inclusive)
+ *
+ * @return one after last line in this region.
+ */
+ public int getEnd() {
+ return end;
+ }
+
+
+ /**
+ * Path of the file this region belongs to
+ *
+ * @return path in the repo/commit
+ */
+ public String getSourcePath() {
+ return sourcePath;
+ }
+
+ /**
+ * Commit this region belongs to
+ *
+ * @return commit for this region
+ */
+ public ObjectId getSourceCommit() {
+ return sourceCommit;
+ }
+
+ @Override
+ public int compareTo(CacheRegion o) {
+ return start - o.start;
+ }
+
+ @SuppressWarnings("nls")
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (sourceCommit != null) {
+ sb.append(sourceCommit.name(), 0, 7).append(' ')
+ .append(" (")
+ .append(sourcePath).append(')');
+ } else {
+ sb.append("<unblamed region>");
+ }
+ sb.append(' ').append("start=").append(start).append(", count=")
+ .append(end - start);
+ return sb.toString();
+ }
+
+ private static void allOrNoneNull(String path, ObjectId commit) {
+ if (path != null && commit != null) {
+ return;
+ }
+
+ if (path == null && commit == null) {
+ return;
+ }
+ throw new IllegalArgumentException(MessageFormat
+ .format(JGitText.get().cacheRegionAllOrNoneNull, path, commit));
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java
index 2526cbfae4..7821efd2a6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffAlgorithm.java
@@ -58,6 +58,8 @@ public abstract class DiffAlgorithm {
/**
* Compare two sequences and identify a list of edits between them.
*
+ * @param <S>
+ * type of sequence being compared.
* @param cmp
* the comparator supplying the element equivalence function.
* @param a
@@ -217,6 +219,8 @@ public abstract class DiffAlgorithm {
* method, which invokes this method using
* {@link org.eclipse.jgit.diff.Subsequence}s.
*
+ * @param <S>
+ * type of sequence being compared.
* @param cmp
* the comparator supplying the element equivalence function.
* @param a
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffDriver.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffDriver.java
new file mode 100644
index 0000000000..b74444400e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffDriver.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2024 Qualcomm Innovation Center, 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 v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.diff;
+
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * Built-in drivers for various languages, sorted by name. These drivers will be
+ * used to determine function names for a hunk.
+ * <p>
+ * When writing or updating patterns, assume the contents are syntactically
+ * correct. Patterns can be simple and need not cover all syntactical corner
+ * cases, as long as they are sufficiently permissive.
+ *
+ * @since 6.10.1
+ */
+@SuppressWarnings({"ImmutableEnumChecker", "nls"})
+public enum DiffDriver {
+ /**
+ * Built-in diff driver for <a href=
+ * "https://learn.microsoft.com/en-us/cpp/cpp/cpp-language-reference">c++</a>
+ */
+ cpp(List.of(
+ /* Jump targets or access declarations */
+ "^[ \\t]*[A-Za-z_][A-Za-z_0-9]*:\\s*($|/[/*])"), List.of(
+ /* functions/methods, variables, and compounds at top level */
+ "^((::\\s*)?[A-Za-z_].*)$")),
+ /**
+ * Built-in diff driver for <a href=
+ * "https://devicetree-specification.readthedocs.io/en/stable/source-language.html">device
+ * tree files</a>
+ */
+ dts(List.of(";", "="), List.of(
+ /* lines beginning with a word optionally preceded by '&' or the root */
+ "^[ \\t]*((/[ \\t]*\\{|&?[a-zA-Z_]).*)")),
+ /**
+ * Built-in diff driver for <a href=
+ * "https://docs.oracle.com/javase/specs/jls/se21/html/index.html">java</a>
+ */
+ java(List.of(
+ "^[ \\t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)"),
+ List.of(
+ /* Class, enum, interface, and record declarations */
+ "^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[ \\t]+.*)$",
+ /* Method definitions; note that constructor signatures are not */
+ /* matched because they are indistinguishable from method calls. */
+ "^[ \\t]*(([A-Za-z_<>&\\]\\[][?&<>.,A-Za-z_0-9]*[ \\t]+)+[A-Za-z_]"
+ + "[A-Za-z_0-9]*[ \\t]*\\([^;]*)$")),
+ /**
+ * Built-in diff driver for
+ * <a href="https://docs.python.org/3/reference/index.html">python</a>
+ */
+ python(List.of("^[ \\t]*((class|(async[ \\t]+)?def)[ \\t].*)$")),
+ /**
+ * Built-in diff driver for
+ * <a href="https://doc.rust-lang.org/reference/introduction.html">rust</a>
+ */
+ rust(List.of("^[\\t ]*((pub(\\([^\\)]+\\))?[\\t ]+)?"
+ + "((async|const|unsafe|extern([\\t ]+\"[^\"]+\"))[\\t ]+)?"
+ + "(struct|enum|union|mod|trait|fn|impl|macro_rules!)[< \\t]+[^;]*)$"));
+
+ private final List<Pattern> negatePatterns;
+
+ private final List<Pattern> matchPatterns;
+
+ DiffDriver(List<String> negate, List<String> match, int flags) {
+ if (negate != null) {
+ this.negatePatterns = negate.stream()
+ .map(r -> Pattern.compile(r, flags))
+ .collect(Collectors.toList());
+ } else {
+ this.negatePatterns = null;
+ }
+ this.matchPatterns = match.stream().map(r -> Pattern.compile(r, flags))
+ .collect(Collectors.toList());
+ }
+
+ DiffDriver(List<String> match) {
+ this(null, match, 0);
+ }
+
+ DiffDriver(List<String> negate, List<String> match) {
+ this(negate, match, 0);
+ }
+
+ /**
+ * Returns the list of patterns used to exclude certain lines from being
+ * considered as function names.
+ *
+ * @return the list of negate patterns
+ */
+ public List<Pattern> getNegatePatterns() {
+ return negatePatterns;
+ }
+
+ /**
+ * Returns the list of patterns used to match lines for potential function
+ * names.
+ *
+ * @return the list of match patterns
+ */
+ public List<Pattern> getMatchPatterns() {
+ return matchPatterns;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
index f0ce121f73..f8c5399983 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
@@ -379,6 +379,8 @@ public class DiffEntry {
}
/**
+ * Get diff attribute
+ *
* @return the {@link Attribute} determining filters to be applied.
* @since 4.11
*/
@@ -457,7 +459,6 @@ public class DiffEntry {
/**
* Whether the mark tree filter with the specified index matched during scan
* or not, see {@link #scan(TreeWalk, boolean, TreeFilter...)}. Example:
- * <p>
*
* <pre>
* TreeFilter filterA = ...;
@@ -506,7 +507,6 @@ public class DiffEntry {
return side == Side.OLD ? getOldId() : getNewId();
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
index 1a5f74f98a..cbac3f90b7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
@@ -30,7 +30,9 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.regex.Pattern;
import org.eclipse.jgit.api.errors.CanceledException;
+import org.eclipse.jgit.attributes.Attribute;
import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.dircache.DirCacheIterator;
@@ -703,7 +705,7 @@ public class DiffFormatter implements AutoCloseable {
*/
public void format(DiffEntry ent) throws IOException {
FormatResult res = createFormatResult(ent);
- format(res.header, res.a, res.b);
+ format(res.header, res.a, res.b, getDiffDriver(ent));
}
private static byte[] writeGitLinkText(AbbreviatedObjectId id) {
@@ -749,11 +751,14 @@ public class DiffFormatter implements AutoCloseable {
* text source for the post-image version of the content. This
* must match the content of
* {@link org.eclipse.jgit.patch.FileHeader#getNewId()}.
+ * @param diffDriver
+ * the diff driver used to obtain function names in hunk headers
* @throws java.io.IOException
- * writing to the supplied stream failed.
+ * writing to the supplied stream failed.
+ * @since 6.10.1
*/
- public void format(FileHeader head, RawText a, RawText b)
- throws IOException {
+ public void format(FileHeader head, RawText a, RawText b,
+ DiffDriver diffDriver) 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.
@@ -763,8 +768,49 @@ public class DiffFormatter implements AutoCloseable {
if (!head.getHunks().isEmpty())
end = head.getHunks().get(0).getStartOffset();
out.write(head.getBuffer(), start, end - start);
- if (head.getPatchType() == PatchType.UNIFIED)
- format(head.toEditList(), a, b);
+ if (head.getPatchType() == PatchType.UNIFIED) {
+ format(head.toEditList(), a, b, diffDriver);
+ }
+ }
+
+ /**
+ * Format a patch script, reusing a previously parsed FileHeader.
+ * <p>
+ * This formatter is primarily useful for editing an existing patch script
+ * to increase or reduce the number of lines of context within the script.
+ * All header lines are reused as-is from the supplied FileHeader.
+ *
+ * @param 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 org.eclipse.jgit.patch.FileHeader#getOldId()}.
+ * @param b
+ * text source for the post-image version of the content. This must match
+ * the content of {@link org.eclipse.jgit.patch.FileHeader#getNewId()}.
+ * @throws java.io.IOException
+ * writing to the supplied stream failed.
+ */
+ public void format(FileHeader head, RawText a, RawText b)
+ throws IOException {
+ format(head, a, b, null);
+ }
+
+ /**
+ * Formats a list of edits in unified diff format
+ *
+ * @param edits
+ * some differences which have been calculated between A and B
+ * @param a
+ * the text A which was compared
+ * @param b
+ * the text B which was compared
+ * @throws java.io.IOException
+ * if an IO error occurred
+ */
+ public void format(EditList edits, RawText a, RawText b)
+ throws IOException {
+ format(edits, a, b, null);
}
/**
@@ -776,10 +822,14 @@ public class DiffFormatter implements AutoCloseable {
* the text A which was compared
* @param b
* the text B which was compared
+ * @param diffDriver
+ * the diff driver used to obtain function names in hunk headers
* @throws java.io.IOException
+ * if an IO error occurred
+ * @since 6.10.1
*/
- public void format(EditList edits, RawText a, RawText b)
- throws IOException {
+ public void format(EditList edits, RawText a, RawText b,
+ DiffDriver diffDriver) throws IOException {
for (int curIdx = 0; curIdx < edits.size();) {
Edit curEdit = edits.get(curIdx);
final int endIdx = findCombinedEnd(edits, curIdx);
@@ -790,7 +840,8 @@ public class DiffFormatter implements AutoCloseable {
final int aEnd = (int) Math.min(a.size(), (long) endEdit.getEndA() + context);
final int bEnd = (int) Math.min(b.size(), (long) endEdit.getEndB() + context);
- writeHunkHeader(aCur, aEnd, bCur, bEnd);
+ writeHunkHeader(aCur, aEnd, bCur, bEnd,
+ getFuncName(a, aCur - 1, diffDriver));
while (aCur < aEnd || bCur < bEnd) {
if (aCur < curEdit.getBeginA() || endIdx + 1 < curIdx) {
@@ -825,6 +876,7 @@ public class DiffFormatter implements AutoCloseable {
* @param line
* the line number within text
* @throws java.io.IOException
+ * if an IO error occurred
*/
protected void writeContextLine(RawText text, int line)
throws IOException {
@@ -843,6 +895,7 @@ public class DiffFormatter implements AutoCloseable {
* @param line
* the line number within text
* @throws java.io.IOException
+ * if an IO error occurred
*/
protected void writeAddedLine(RawText text, int line)
throws IOException {
@@ -857,6 +910,7 @@ public class DiffFormatter implements AutoCloseable {
* @param line
* the line number within text
* @throws java.io.IOException
+ * if an IO error occurred
*/
protected void writeRemovedLine(RawText text, int line)
throws IOException {
@@ -875,9 +929,32 @@ public class DiffFormatter implements AutoCloseable {
* @param bEndLine
* within second source
* @throws java.io.IOException
+ * if an IO error occurred
*/
- protected void writeHunkHeader(int aStartLine, int aEndLine,
- int bStartLine, int bEndLine) throws IOException {
+ protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine,
+ int bEndLine) throws IOException {
+ writeHunkHeader(aStartLine, aEndLine, bStartLine, bEndLine, null);
+ }
+
+ /**
+ * Output a hunk header
+ *
+ * @param aStartLine
+ * within first source
+ * @param aEndLine
+ * within first source
+ * @param bStartLine
+ * within second source
+ * @param bEndLine
+ * within second source
+ * @param funcName
+ * function name of this hunk
+ * @throws java.io.IOException
+ * if an IO error occurred
+ * @since 6.10.1
+ */
+ protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine,
+ int bEndLine, String funcName) throws IOException {
out.write('@');
out.write('@');
writeRange('-', aStartLine + 1, aEndLine - aStartLine);
@@ -885,6 +962,10 @@ public class DiffFormatter implements AutoCloseable {
out.write(' ');
out.write('@');
out.write('@');
+ if (funcName != null) {
+ out.write(' ');
+ out.write(funcName.getBytes());
+ }
out.write('\n');
}
@@ -1242,4 +1323,50 @@ public class DiffFormatter implements AutoCloseable {
private static boolean end(Edit edit, int a, int b) {
return edit.getEndA() <= a && edit.getEndB() <= b;
}
+
+ private String getFuncName(RawText text, int startAt,
+ DiffDriver diffDriver) {
+ if (diffDriver != null) {
+ while (startAt > 0) {
+ String line = text.getString(startAt);
+ startAt--;
+ if (matchesAny(diffDriver.getNegatePatterns(), line)) {
+ continue;
+ }
+ if (matchesAny(diffDriver.getMatchPatterns(), line)) {
+ String funcName = line.replaceAll("^[ \\t]+", ""); //$NON-NLS-1$//$NON-NLS-2$
+ return funcName.substring(0,
+ Math.min(funcName.length(), 80)).trim();
+ }
+ }
+ }
+ return null;
+ }
+
+ private boolean matchesAny(List<Pattern> patterns, String text) {
+ if (patterns != null) {
+ for (Pattern p : patterns) {
+ if (p.matcher(text).find()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private DiffDriver getDiffDriver(DiffEntry entry) {
+ Attribute diffAttr = entry.getDiffAttribute();
+ if (diffAttr == null) {
+ return null;
+ }
+ String diffAttrValue = diffAttr.getValue();
+ if (diffAttrValue == null) {
+ return null;
+ }
+ try {
+ return DiffDriver.valueOf(diffAttrValue);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java
index 219a187e27..2968dbaa8f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java
@@ -238,13 +238,11 @@ public class Edit {
endB = sEnd;
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
return beginA ^ endA;
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object o) {
if (o instanceof Edit) {
@@ -255,7 +253,6 @@ public class Edit {
return false;
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/EditList.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/EditList.java
index d40a64099a..85e23e9a92 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/EditList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/EditList.java
@@ -49,7 +49,6 @@ public class EditList extends ArrayList<Edit> {
super(capacity);
}
- /** {@inheritDoc} */
@Override
public String toString() {
return "EditList" + super.toString(); //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequence.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequence.java
index 9cb8e7364b..82ab21c11e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequence.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequence.java
@@ -34,7 +34,6 @@ public final class HashedSequence<S extends Sequence> extends Sequence {
this.hashes = hashes;
}
- /** {@inheritDoc} */
@Override
public int size() {
return base.size();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequenceComparator.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequenceComparator.java
index 0380208565..c7dfd9a18f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequenceComparator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/HashedSequenceComparator.java
@@ -33,7 +33,6 @@ public final class HashedSequenceComparator<S extends Sequence> extends
this.cmp = cmp;
}
- /** {@inheritDoc} */
@Override
public boolean equals(HashedSequence<S> a, int ai, //
HashedSequence<S> b, int bi) {
@@ -41,7 +40,6 @@ public final class HashedSequenceComparator<S extends Sequence> extends
&& cmp.equals(a.base, ai, b.base, bi);
}
- /** {@inheritDoc} */
@Override
public int hash(HashedSequence<S> seq, int ptr) {
return seq.hashes[ptr];
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java
index 4035a1e48e..bb72e1faed 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/HistogramDiff.java
@@ -98,7 +98,6 @@ public class HistogramDiff extends LowLevelDiffAlgorithm {
maxChainLength = maxLen;
}
- /** {@inheritDoc} */
@Override
public <S extends Sequence> void diffNonCommon(EditList edits,
HashedSequenceComparator<S> cmp, HashedSequence<S> a,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/LowLevelDiffAlgorithm.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/LowLevelDiffAlgorithm.java
index 7f3c251864..39be43d12d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/LowLevelDiffAlgorithm.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/LowLevelDiffAlgorithm.java
@@ -14,7 +14,6 @@ package org.eclipse.jgit.diff;
* Compares two sequences primarily based upon hash codes.
*/
public abstract class LowLevelDiffAlgorithm extends DiffAlgorithm {
- /** {@inheritDoc} */
@Override
public <S extends Sequence> EditList diffNonCommon(
SequenceComparator<? super S> cmp, S a, S b) {
@@ -40,6 +39,8 @@ public abstract class LowLevelDiffAlgorithm extends DiffAlgorithm {
* method, which invokes this method using
* {@link org.eclipse.jgit.diff.Subsequence}s.
*
+ * @param <S>
+ * type of Sequence compared
* @param edits
* result list to append the region's edits onto.
* @param cmp
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java
index cf6826ab92..bedb0b335c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java
@@ -11,6 +11,10 @@
package org.eclipse.jgit.diff;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
import java.text.MessageFormat;
import org.eclipse.jgit.errors.DiffInterruptedException;
@@ -330,7 +334,7 @@ if (k < beginK || k > endK)
abstract boolean meets(int d, int k, int x, long snake);
final long newSnake(int k, int x) {
- long y = k + x;
+ long y = (long) k + x;
long ret = ((long) x) << 32;
return ret | y;
}
@@ -531,7 +535,7 @@ if (k < beginK || k > endK)
*/
public static void main(String[] args) {
if (args.length != 2) {
- System.err.println(JGitText.get().need2Arguments);
+ err().println(JGitText.get().need2Arguments);
System.exit(1);
}
try {
@@ -540,7 +544,13 @@ if (k < beginK || k > endK)
EditList r = INSTANCE.diff(RawTextComparator.DEFAULT, a, b);
System.out.println(r.toString());
} catch (Exception e) {
- e.printStackTrace();
+ PrintWriter err = err();
+ err.println(e.getMessage());
+ e.printStackTrace(err);
}
}
+
+ private static PrintWriter err() {
+ return new PrintWriter(new OutputStreamWriter(System.err, UTF_8));
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java
index 53cd2ea5d7..b401bbe73d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java
@@ -43,14 +43,12 @@ public class PatchIdDiffFormatter extends DiffFormatter {
return ObjectId.fromRaw(digest.digest());
}
- /** {@inheritDoc} */
@Override
- protected void writeHunkHeader(int aStartLine, int aEndLine,
- int bStartLine, int bEndLine) throws IOException {
+ protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine,
+ int bEndLine, String funcName) throws IOException {
// The hunk header is not taken into account for patch id calculation
}
- /** {@inheritDoc} */
@Override
protected void formatIndexLine(OutputStream o, DiffEntry ent)
throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java
index b52803513d..fdfe533618 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java
@@ -106,6 +106,8 @@ public class RawText extends Sequence {
}
/**
+ * Get the raw content
+ *
* @return the raw, unprocessed content read.
* @since 4.11
*/
@@ -114,7 +116,6 @@ public class RawText extends Sequence {
}
/** @return total number of items in the sequence. */
- /** {@inheritDoc} */
@Override
public int size() {
// The line map is always 2 entries larger than the number of lines in
@@ -359,18 +360,22 @@ public class RawText extends Sequence {
length = maxLength;
isComplete = false;
}
- byte last = 'x'; // Just something inconspicuous.
- for (int ptr = 0; ptr < length; ptr++) {
- byte curr = raw[ptr];
- if (isBinary(curr, last)) {
+
+ int ptr = -1;
+ byte current;
+ while (ptr < length - 2) {
+ current = raw[++ptr];
+ if (current == '\0' || (current == '\r' && raw[++ptr] != '\n')) {
return true;
}
- last = curr;
}
- if (isComplete) {
- // Buffer contains everything...
- return last == '\r'; // ... so this must be a lone CR
+
+ if (ptr == length - 2) {
+ // if '\r' be last, then if isComplete then return binary
+ current = raw[++ptr];
+ return current == '\0' || (current == '\r' && isComplete);
}
+
return false;
}
@@ -466,26 +471,30 @@ public class RawText extends Sequence {
*/
public static boolean isCrLfText(byte[] raw, int length, boolean complete) {
boolean has_crlf = false;
- byte last = 'x'; // Just something inconspicuous
- for (int ptr = 0; ptr < length; ptr++) {
- byte curr = raw[ptr];
- if (isBinary(curr, last)) {
+
+ int ptr = -1;
+ byte current;
+ while (ptr < length - 2) {
+ current = raw[++ptr];
+ if (current == '\0') {
return false;
}
- if (curr == '\n' && last == '\r') {
+ if (current == '\r') {
+ if (raw[++ptr] != '\n') {
+ return false;
+ }
has_crlf = true;
}
- last = curr;
}
- if (last == '\r') {
- if (complete) {
- // Lone CR: it's binary after all.
+
+ if (ptr == length - 2) {
+ // if '\r' be last, then if isComplete then return binary
+ current = raw[++ptr];
+ if (current == '\0' || (current == '\r' && complete)) {
return false;
}
- // Tough call. If the next byte, which we don't have, would be a
- // '\n', it'd be a CR-LF text, otherwise it'd be binary. Just decide
- // based on what we already scanned; it wasn't binary until now.
}
+
return has_crlf;
}
@@ -577,4 +586,5 @@ public class RawText extends Sequence {
return new RawText(data, RawParseUtils.lineMapOrBinary(data, 0, (int) sz));
}
}
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawTextComparator.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawTextComparator.java
index 0c41b8598b..e06e2f8290 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawTextComparator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawTextComparator.java
@@ -227,7 +227,6 @@ public abstract class RawTextComparator extends SequenceComparator<RawText> {
return hashRegion(seq.content, begin, end);
}
- /** {@inheritDoc} */
@Override
public Edit reduceCommonStartEnd(RawText a, RawText b, Edit e) {
// This is a faster exact match based form that tries to improve
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
index c33f53adde..fd84bc6c87 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RenameDetector.java
@@ -713,15 +713,16 @@ public class RenameDetector {
/**
* Find the best match by file path for a given DiffEntry from a list of
- * DiffEntrys. The returned DiffEntry will be of the same type as <src>. If
- * no DiffEntry can be found that has the same type, this method will return
- * null.
+ * DiffEntrys. The returned DiffEntry will be of the same type as
+ * &lt;src&gt;. If no DiffEntry can be found that has the same type, this
+ * method will return null.
*
* @param src
* the DiffEntry to try to find a match for
* @param list
* a list of DiffEntrys to search through
- * @return the DiffEntry from <list> who's file path best matches <src>
+ * @return the DiffEntry from &lt;list&gt; who's file path best matches
+ * &lt;src&gt;
*/
private static DiffEntry bestPathMatch(DiffEntry src, List<DiffEntry> list) {
DiffEntry best = null;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
index 9ac94895b1..fb98df7c9e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
@@ -80,7 +80,7 @@ class SimilarityRenameDetector {
private long[] matrix;
/** Score a pair must exceed to be considered a rename. */
- private int renameScore = 60;
+ private int renameScore = 50;
/**
* File size threshold (in bytes) for detecting renames. Files larger
@@ -407,6 +407,7 @@ class SimilarityRenameDetector {
| encodeFile(dstIdx);
}
+ @SuppressWarnings("IntLongMath")
private static long encodeFile(int idx) {
// We invert the index so that the first file in the list sorts
// later in the table. This permits us to break ties favoring
@@ -415,6 +416,7 @@ class SimilarityRenameDetector {
return INDEX_MASK - idx;
}
+ @SuppressWarnings("IntLongMath")
private static int decodeFile(int v) {
return INDEX_MASK - v;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/Subsequence.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Subsequence.java
index 61f133ca55..878c66b30f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/Subsequence.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Subsequence.java
@@ -25,6 +25,8 @@ public final class Subsequence<S extends Sequence> extends Sequence {
/**
* Construct a subsequence around the A region/base sequence.
*
+ * @param <S>
+ * type of returned Sequence
* @param a
* the A sequence.
* @param region
@@ -38,6 +40,8 @@ public final class Subsequence<S extends Sequence> extends Sequence {
/**
* Construct a subsequence around the B region/base sequence.
*
+ * @param <S>
+ * type of returned Sequence
* @param b
* the B sequence.
* @param region
@@ -51,6 +55,8 @@ public final class Subsequence<S extends Sequence> extends Sequence {
/**
* Adjust the Edit to reflect positions in the base sequence.
*
+ * @param <S>
+ * type of returned Sequence
* @param e
* edit to adjust in-place. Prior to invocation the indexes are
* in terms of the two subsequences; after invocation the indexes
@@ -72,6 +78,8 @@ public final class Subsequence<S extends Sequence> extends Sequence {
/**
* Adjust the Edits to reflect positions in the base sequence.
*
+ * @param <S>
+ * type of returned Sequence
* @param edits
* edits to adjust in-place. Prior to invocation the indexes are
* in terms of the two subsequences; after invocation the indexes
@@ -116,7 +124,6 @@ public final class Subsequence<S extends Sequence> extends Sequence {
this.size = end - begin;
}
- /** {@inheritDoc} */
@Override
public int size() {
return size;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SubsequenceComparator.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SubsequenceComparator.java
index c60945361f..707df59259 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SubsequenceComparator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SubsequenceComparator.java
@@ -36,13 +36,11 @@ public final class SubsequenceComparator<S extends Sequence> extends
this.cmp = cmp;
}
- /** {@inheritDoc} */
@Override
public boolean equals(Subsequence<S> a, int ai, Subsequence<S> b, int bi) {
return cmp.equals(a.base, ai + a.begin, b.base, bi + b.begin);
}
- /** {@inheritDoc} */
@Override
public int hash(Subsequence<S> seq, int ptr) {
return cmp.hash(seq.base, ptr + seq.begin);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java
index 86e60e5b73..68296ef330 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java
@@ -25,9 +25,11 @@ import org.eclipse.jgit.errors.DirCacheNameConflictException;
* services to applications.
*/
abstract class BaseDirCacheEditor {
+ @SuppressWarnings("doclint:missing")
/** The cache instance this editor updates during {@link #finish()}. */
protected DirCache cache;
+ @SuppressWarnings("doclint:missing")
/**
* Entry table this builder will eventually replace into {@link #cache}.
* <p>
@@ -40,7 +42,7 @@ abstract class BaseDirCacheEditor {
*/
protected DirCacheEntry[] entries;
- /** Total number of valid entries in {@link #entries}. */
+ /** Total number of valid entries in {@link BaseDirCacheEditor#entries}. */
protected int entryCnt;
/**
@@ -137,7 +139,8 @@ abstract class BaseDirCacheEditor {
public abstract void finish();
/**
- * Update the DirCache with the contents of {@link #entries}.
+ * Update the DirCache with the contents of
+ * {@link BaseDirCacheEditor#entries}.
* <p>
* This method should be invoked only during an implementation of
* {@link #finish()}, and only after {@link #entries} is sorted.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/Checkout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/Checkout.java
new file mode 100644
index 0000000000..de02aecdb9
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/Checkout.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2023, Thomas Wolf <twolf@apache.org> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.dircache;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.StandardCopyOption;
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.FileModeCache;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
+import org.eclipse.jgit.lib.CoreConfig.SymLinks;
+import org.eclipse.jgit.lib.FileModeCache.CacheItem;
+import org.eclipse.jgit.treewalk.WorkingTreeOptions;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * An object that can be used to check out many files.
+ *
+ * @since 6.6.1
+ */
+public class Checkout {
+
+ private final FileModeCache cache;
+
+ private final WorkingTreeOptions options;
+
+ private boolean recursiveDelete;
+
+ /**
+ * Creates a new {@link Checkout} for checking out from the given
+ * repository.
+ *
+ * @param repo
+ * the {@link Repository} to check out from
+ */
+ public Checkout(@NonNull Repository repo) {
+ this(repo, null);
+ }
+
+ /**
+ * Creates a new {@link Checkout} for checking out from the given
+ * repository.
+ *
+ * @param repo
+ * the {@link Repository} to check out from
+ * @param options
+ * the {@link WorkingTreeOptions} to use; if {@code null},
+ * read from the {@code repo} config when this object is
+ * created
+ */
+ public Checkout(@NonNull Repository repo, WorkingTreeOptions options) {
+ this.cache = new FileModeCache(repo);
+ this.options = options != null ? options
+ : repo.getConfig().get(WorkingTreeOptions.KEY);
+ }
+
+ /**
+ * Retrieves the {@link WorkingTreeOptions} of the repository that are
+ * used.
+ *
+ * @return the {@link WorkingTreeOptions}
+ */
+ public WorkingTreeOptions getWorkingTreeOptions() {
+ return options;
+ }
+
+ /**
+ * Defines whether directories that are in the way of the file to be checked
+ * out shall be deleted recursively.
+ *
+ * @param recursive
+ * whether to delete such directories recursively
+ * @return {@code this}
+ */
+ public Checkout setRecursiveDeletion(boolean recursive) {
+ this.recursiveDelete = recursive;
+ return this;
+ }
+
+ /**
+ * Ensure that the given parent directory exists, and cache the information
+ * that gitPath refers to a file.
+ *
+ * @param gitPath
+ * of the file to be written
+ * @param parentDir
+ * directory in which the file shall be placed, assumed to be the
+ * parent of the {@code gitPath}
+ * @param makeSpace
+ * whether to delete a possibly existing file at
+ * {@code parentDir}
+ * @throws IOException
+ * if the directory cannot be created, if necessary
+ */
+ public void safeCreateParentDirectory(String gitPath, File parentDir,
+ boolean makeSpace) throws IOException {
+ cache.safeCreateParentDirectory(gitPath, parentDir, makeSpace);
+ }
+
+ /**
+ * Checks out the gitlink given by the {@link DirCacheEntry}.
+ *
+ * @param entry
+ * {@link DirCacheEntry} to check out
+ * @param gitPath
+ * the git path of the entry, if known already; otherwise
+ * {@code null} and it's read from the entry itself
+ * @throws IOException
+ * if the gitlink cannot be checked out
+ */
+ public void checkoutGitlink(DirCacheEntry entry, String gitPath)
+ throws IOException {
+ FS fs = cache.getRepository().getFS();
+ File workingTree = cache.getRepository().getWorkTree();
+ String path = gitPath != null ? gitPath : entry.getPathString();
+ File gitlinkDir = new File(workingTree, path);
+ File parentDir = gitlinkDir.getParentFile();
+ CacheItem cachedParent = cache.safeCreateDirectory(path, parentDir,
+ false);
+ FileUtils.mkdirs(gitlinkDir, true);
+ cachedParent.insert(path.substring(path.lastIndexOf('/') + 1),
+ FileMode.GITLINK);
+ entry.setLastModified(fs.lastModifiedInstant(gitlinkDir));
+ }
+
+ /**
+ * Checks out the file given by the {@link DirCacheEntry}.
+ *
+ * @param entry
+ * {@link DirCacheEntry} to check out
+ * @param metadata
+ * {@link CheckoutMetadata} to use for CR/LF handling and
+ * smudge filtering
+ * @param reader
+ * {@link ObjectReader} to use
+ * @param gitPath
+ * the git path of the entry, if known already; otherwise
+ * {@code null} and it's read from the entry itself
+ * @throws IOException
+ * if the file cannot be checked out
+ */
+ public void checkout(DirCacheEntry entry, CheckoutMetadata metadata,
+ ObjectReader reader, String gitPath) throws IOException {
+ if (metadata == null) {
+ metadata = CheckoutMetadata.EMPTY;
+ }
+ FS fs = cache.getRepository().getFS();
+ ObjectLoader ol = reader.open(entry.getObjectId());
+ String path = gitPath != null ? gitPath : entry.getPathString();
+ File f = new File(cache.getRepository().getWorkTree(), path);
+ File parentDir = f.getParentFile();
+ CacheItem cachedParent = cache.safeCreateDirectory(path, parentDir,
+ true);
+ if (entry.getFileMode() == FileMode.SYMLINK
+ && options.getSymLinks() == SymLinks.TRUE) {
+ byte[] bytes = ol.getBytes();
+ String target = RawParseUtils.decode(bytes);
+ if (recursiveDelete && Files.isDirectory(f.toPath(),
+ LinkOption.NOFOLLOW_LINKS)) {
+ FileUtils.delete(f, FileUtils.RECURSIVE);
+ }
+ fs.createSymLink(f, target);
+ cachedParent.insert(f.getName(), FileMode.SYMLINK);
+ entry.setLength(bytes.length);
+ entry.setLastModified(fs.lastModifiedInstant(f));
+ return;
+ }
+
+ String name = f.getName();
+ if (name.length() > 200) {
+ name = name.substring(0, 200);
+ }
+ File tmpFile = File.createTempFile("._" + name, null, parentDir); //$NON-NLS-1$
+
+ DirCacheCheckout.getContent(cache.getRepository(), path, metadata, ol,
+ options,
+ new FileOutputStream(tmpFile));
+
+ // The entry needs to correspond to the on-disk file size. If the
+ // content was filtered (either by autocrlf handling or smudge
+ // filters) ask the file system again for the length. Otherwise the
+ // object loader knows the size
+ if (metadata.eolStreamType == EolStreamType.DIRECT
+ && metadata.smudgeFilterCommand == null) {
+ entry.setLength(ol.getSize());
+ } else {
+ entry.setLength(tmpFile.length());
+ }
+
+ if (options.isFileMode() && fs.supportsExecute()) {
+ if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) {
+ if (!fs.canExecute(tmpFile))
+ fs.setExecute(tmpFile, true);
+ } else {
+ if (fs.canExecute(tmpFile))
+ fs.setExecute(tmpFile, false);
+ }
+ }
+ try {
+ boolean isDir = Files.isDirectory(f.toPath(),
+ LinkOption.NOFOLLOW_LINKS);
+ if (recursiveDelete && isDir) {
+ FileUtils.delete(f, FileUtils.RECURSIVE);
+ }
+ if (cache.getRepository().isWorkTreeCaseInsensitive() && !isDir) {
+ // We cannot rely on rename via Files.move() to work correctly
+ // if the target exists in a case variant. For instance with JDK
+ // 17 on Mac OS, the existing case-variant name is kept. On
+ // Windows 11 it would work and use the name given in 'f'.
+ FileUtils.delete(f, FileUtils.SKIP_MISSING);
+ }
+ FileUtils.rename(tmpFile, f, StandardCopyOption.ATOMIC_MOVE);
+ cachedParent.remove(f.getName());
+ } catch (IOException e) {
+ throw new IOException(
+ MessageFormat.format(JGitText.get().renameFileFailed,
+ tmpFile.getPath(), f.getPath()),
+ e);
+ } finally {
+ if (tmpFile.exists()) {
+ FileUtils.delete(tmpFile);
+ }
+ }
+ entry.setLastModified(fs.lastModifiedInstant(f));
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
index e56061223c..c650d6e8e7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
@@ -429,6 +429,7 @@ public class DirCache {
*
* @return {@code true} if the memory state differs from the index file
* @throws java.io.IOException
+ * if an IO error occurred
*/
public boolean isOutdated() throws IOException {
if (liveFile == null || !liveFile.exists())
@@ -1001,6 +1002,7 @@ public class DirCache {
* Update any smudged entries with information from the working tree.
*
* @throws IOException
+ * if an IO error occurred
*/
private void updateSmudgedEntries() throws IOException {
List<String> paths = new ArrayList<>(128);
@@ -1035,7 +1037,12 @@ public class DirCache {
}
}
- enum DirCacheVersion implements ConfigEnum {
+ /**
+ * DirCache versions
+ *
+ * @since 7.2
+ */
+ public enum DirCacheVersion implements ConfigEnum {
/** Minimum index version on-disk format that we support. */
DIRC_VERSION_MINIMUM(2),
@@ -1058,6 +1065,9 @@ public class DirCache {
this.version = versionCode;
}
+ /**
+ * @return the version code for this version
+ */
public int getVersionCode() {
return version;
}
@@ -1076,6 +1086,13 @@ public class DirCache {
}
}
+ /**
+ * Create DirCacheVersion from integer value of the version code.
+ *
+ * @param val
+ * integer value of the version code.
+ * @return the DirCacheVersion instance of the version code.
+ */
public static DirCacheVersion fromInt(int val) {
for (DirCacheVersion v : DirCacheVersion.values()) {
if (val == v.getVersionCode()) {
@@ -1096,9 +1113,8 @@ public class DirCache {
boolean manyFiles = cfg.getBoolean(
ConfigConstants.CONFIG_FEATURE_SECTION,
ConfigConstants.CONFIG_KEY_MANYFILES, false);
- indexVersion = cfg.getEnum(DirCacheVersion.values(),
- ConfigConstants.CONFIG_INDEX_SECTION, null,
- ConfigConstants.CONFIG_KEY_VERSION,
+ indexVersion = cfg.getEnum(ConfigConstants.CONFIG_INDEX_SECTION,
+ null, ConfigConstants.CONFIG_KEY_VERSION,
manyFiles ? DirCacheVersion.DIRC_VERSION_PATHCOMPRESS
: DirCacheVersion.DIRC_VERSION_EXTENDED);
skipHash = cfg.getBoolean(ConfigConstants.CONFIG_INDEX_SECTION,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java
index f6e4d360a6..befd8067ab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java
@@ -74,7 +74,6 @@ public class DirCacheBuildIterator extends DirCacheIterator {
builder = p.builder;
}
- /** {@inheritDoc} */
@Override
public AbstractTreeIterator createSubtreeIterator(ObjectReader reader)
throws IncorrectObjectTypeException, IOException {
@@ -84,7 +83,6 @@ public class DirCacheBuildIterator extends DirCacheIterator {
return new DirCacheBuildIterator(this, currentSubtree);
}
- /** {@inheritDoc} */
@Override
public void skip() throws CorruptObjectException {
if (currentSubtree != null)
@@ -94,7 +92,6 @@ public class DirCacheBuildIterator extends DirCacheIterator {
next(1);
}
- /** {@inheritDoc} */
@Override
public void stopWalk() {
final int cur = ptr;
@@ -103,7 +100,6 @@ public class DirCacheBuildIterator extends DirCacheIterator {
builder.keep(cur, cnt - cur);
}
- /** {@inheritDoc} */
@Override
protected boolean needsStopWalk() {
return ptr < cache.getEntryCount();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
index 9fe77f36e8..28d2502005 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java
@@ -186,7 +186,6 @@ public class DirCacheBuilder extends BaseDirCacheEditor {
return e;
}
- /** {@inheritDoc} */
@Override
public void finish() {
if (!sorted)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
index 1fb81b71e9..18d77482e0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -5,7 +5,7 @@
* Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
* Copyright (C) 2010, Chrisian Halstrick <christian.halstrick@sap.com>
* Copyright (C) 2019, 2020, Andre Bossert <andre.bossert@siemens.com>
- * Copyright (C) 2017, 2022, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2017, 2025, Thomas Wolf <twolf@apache.org> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -19,11 +19,9 @@ package org.eclipse.jgit.dircache;
import static org.eclipse.jgit.treewalk.TreeWalk.OperationType.CHECKOUT_OP;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
import java.time.Instant;
import java.util.ArrayList;
@@ -33,6 +31,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeSet;
import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.api.errors.FilterFailedException;
@@ -49,7 +48,6 @@ import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
-import org.eclipse.jgit.lib.CoreConfig.SymLinks;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectChecker;
@@ -69,9 +67,6 @@ import org.eclipse.jgit.treewalk.WorkingTreeOptions;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FS.ExecutionResult;
-import org.eclipse.jgit.util.FileUtils;
-import org.eclipse.jgit.util.IntList;
-import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.SystemReader;
import org.eclipse.jgit.util.io.EolStreamTypeUtil;
import org.slf4j.Logger;
@@ -100,7 +95,9 @@ public class DirCacheCheckout {
/**
* @param eolStreamType
+ * how to convert EOL characters during stream conversion
* @param smudgeFilterCommand
+ * command used as smudge filter during checkout
*/
public CheckoutMetadata(EolStreamType eolStreamType,
String smudgeFilterCommand) {
@@ -116,9 +113,11 @@ public class DirCacheCheckout {
private Map<String, CheckoutMetadata> updated = new LinkedHashMap<>();
+ private Set<String> existing;
+
private ArrayList<String> conflicts = new ArrayList<>();
- private ArrayList<String> removed = new ArrayList<>();
+ private TreeSet<String> removed;
private ArrayList<String> kept = new ArrayList<>();
@@ -144,7 +143,7 @@ public class DirCacheCheckout {
private boolean performingCheckout;
- private WorkingTreeOptions options;
+ private Checkout checkout;
private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
@@ -188,7 +187,7 @@ public class DirCacheCheckout {
* @return a list of all files removed by this checkout
*/
public List<String> getRemoved() {
- return removed;
+ return new ArrayList<>(removed);
}
/**
@@ -206,6 +205,7 @@ public class DirCacheCheckout {
* @param workingTree
* an iterator over the repositories Working Tree
* @throws java.io.IOException
+ * if an IO error occurred
*/
public DirCacheCheckout(Repository repo, ObjectId headCommitTree, DirCache dc,
ObjectId mergeCommitTree, WorkingTreeIterator workingTree)
@@ -216,6 +216,14 @@ public class DirCacheCheckout {
this.mergeCommitTree = mergeCommitTree;
this.workingTree = workingTree;
this.initialCheckout = !repo.isBare() && !repo.getIndexFile().exists();
+ boolean caseInsensitive = !repo.isBare()
+ && repo.isWorkTreeCaseInsensitive();
+ this.removed = caseInsensitive
+ ? new TreeSet<>(String::compareToIgnoreCase)
+ : new TreeSet<>();
+ this.existing = caseInsensitive
+ ? new TreeSet<>(String::compareToIgnoreCase)
+ : null;
}
/**
@@ -233,6 +241,7 @@ public class DirCacheCheckout {
* @param mergeCommitTree
* the id of the tree we want to fast-forward to
* @throws java.io.IOException
+ * if an IO error occurred
*/
public DirCacheCheckout(Repository repo, ObjectId headCommitTree,
DirCache dc, ObjectId mergeCommitTree) throws IOException {
@@ -252,6 +261,7 @@ public class DirCacheCheckout {
* @param workingTree
* an iterator over the repositories Working Tree
* @throws java.io.IOException
+ * if an IO error occurred
*/
public DirCacheCheckout(Repository repo, DirCache dc,
ObjectId mergeCommitTree, WorkingTreeIterator workingTree)
@@ -271,6 +281,7 @@ public class DirCacheCheckout {
* @param mergeCommitTree
* the id of the tree of the
* @throws java.io.IOException
+ * if an IO error occurred
*/
public DirCacheCheckout(Repository repo, DirCache dc,
ObjectId mergeCommitTree) throws IOException {
@@ -294,7 +305,9 @@ public class DirCacheCheckout {
* operations.
*
* @throws org.eclipse.jgit.errors.CorruptObjectException
+ * if a corrupt object was found
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void preScanTwoTrees() throws CorruptObjectException, IOException {
removed.clear();
@@ -324,9 +337,13 @@ public class DirCacheCheckout {
* there is no head yet.
*
* @throws org.eclipse.jgit.errors.MissingObjectException
+ * if an object was found missing
* @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
+ * if an object didn't have the expected type
* @throws org.eclipse.jgit.errors.CorruptObjectException
+ * if an object is corrupt
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void prescanOneTree()
throws MissingObjectException, IncorrectObjectTypeException,
@@ -372,6 +389,7 @@ public class DirCacheCheckout {
* @param f
* the working tree
* @throws IOException
+ * if an IO error occurred
*/
void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
WorkingTreeIterator f) throws IOException {
@@ -392,9 +410,11 @@ public class DirCacheCheckout {
// content to be checked out.
update(m);
}
- } else
+ } else {
update(m);
- } else if (f == null || !m.idEqual(i)) {
+ }
+ } else if (f == null || !m.idEqual(i)
+ || m.getEntryRawMode() != i.getEntryRawMode()) {
// The working tree file is missing or the merge content differs
// from index content
update(m);
@@ -402,11 +422,11 @@ public class DirCacheCheckout {
// The index contains a file (and not a folder)
if (f.isModified(i.getDirCacheEntry(), true,
this.walk.getObjectReader())
- || i.getDirCacheEntry().getStage() != 0)
+ || i.getDirCacheEntry().getStage() != 0) {
// The working tree file is dirty or the index contains a
// conflict
update(m);
- else {
+ } else {
// update the timestamp of the index with the one from the
// file if not set, as we are sure to be in sync here.
DirCacheEntry entry = i.getDirCacheEntry();
@@ -416,9 +436,10 @@ public class DirCacheCheckout {
}
keep(i.getEntryPathString(), entry, f);
}
- } else
+ } else {
// The index contains a folder
keep(i.getEntryPathString(), i.getDirCacheEntry(), f);
+ }
} else {
// There is no entry in the merge commit. Means: we want to delete
// what's currently in the index and working tree
@@ -465,6 +486,7 @@ public class DirCacheCheckout {
* successful and the working tree was updated for all other files.
* <code>true</code> is returned when no such problem occurred
* @throws java.io.IOException
+ * if an IO error occurred
*/
public boolean checkout() throws IOException {
try {
@@ -495,9 +517,8 @@ public class DirCacheCheckout {
MissingObjectException, IncorrectObjectTypeException,
CheckoutConflictException, IndexWriteException, CanceledException {
toBeDeleted.clear();
- options = repo.getConfig()
- .get(WorkingTreeOptions.KEY);
try (ObjectReader objectReader = repo.getObjectDatabase().newReader()) {
+ checkout = new Checkout(repo, null);
if (headCommitTree != null)
preScanTwoTrees();
else
@@ -513,6 +534,13 @@ public class DirCacheCheckout {
// update our index
builder.finish();
+ // On case-insensitive file systems we may have a case variant kept
+ // and another one removed. In that case, don't remove it.
+ if (existing != null) {
+ removed.removeAll(existing);
+ existing.clear();
+ }
+
// init progress reporting
int numTotal = removed.size() + updated.size() + conflicts.size();
monitor.beginTask(JGitText.get().checkingOutFiles, numTotal);
@@ -523,9 +551,9 @@ public class DirCacheCheckout {
// when deleting files process them in the opposite order as they have
// been reported. This ensures the files are deleted before we delete
// their parent folders
- IntList nonDeleted = new IntList();
- for (int i = removed.size() - 1; i >= 0; i--) {
- String r = removed.get(i);
+ Iterator<String> iter = removed.descendingIterator();
+ while (iter.hasNext()) {
+ String r = iter.next();
file = new File(repo.getWorkTree(), r);
if (!file.delete() && repo.getFS().exists(file)) {
// The list of stuff to delete comes from the index
@@ -534,7 +562,7 @@ public class DirCacheCheckout {
// to delete it. A submodule is not empty, so it
// is safe to check this after a failed delete.
if (!repo.getFS().isDirectory(file)) {
- nonDeleted.add(i);
+ iter.remove();
toBeDeleted.add(r);
}
} else {
@@ -552,8 +580,6 @@ public class DirCacheCheckout {
if (file != null) {
removeEmptyParents(file);
}
- removed = filterOut(removed, nonDeleted);
- nonDeleted = null;
Iterator<Map.Entry<String, CheckoutMetadata>> toUpdate = updated
.entrySet().iterator();
Map.Entry<String, CheckoutMetadata> e = null;
@@ -564,10 +590,9 @@ public class DirCacheCheckout {
CheckoutMetadata meta = e.getValue();
DirCacheEntry entry = dc.getEntry(path);
if (FileMode.GITLINK.equals(entry.getRawMode())) {
- checkoutGitlink(path, entry);
+ checkout.checkoutGitlink(entry, path);
} else {
- checkoutEntry(repo, entry, objectReader, false, meta,
- options);
+ checkout.checkout(entry, meta, objectReader, path);
}
e = null;
@@ -602,8 +627,8 @@ public class DirCacheCheckout {
break;
}
if (entry.getStage() == DirCacheEntry.STAGE_3) {
- checkoutEntry(repo, entry, objectReader, false,
- null, options);
+ checkout.checkout(entry, null, objectReader,
+ conflict);
break;
}
++entryIdx;
@@ -626,44 +651,6 @@ public class DirCacheCheckout {
return toBeDeleted.isEmpty();
}
- private void checkoutGitlink(String path, DirCacheEntry entry)
- throws IOException {
- File gitlinkDir = new File(repo.getWorkTree(), path);
- FileUtils.mkdirs(gitlinkDir, true);
- FS fs = repo.getFS();
- entry.setLastModified(fs.lastModifiedInstant(gitlinkDir));
- }
-
- private static ArrayList<String> filterOut(ArrayList<String> strings,
- IntList indicesToRemove) {
- int n = indicesToRemove.size();
- if (n == strings.size()) {
- return new ArrayList<>(0);
- }
- switch (n) {
- case 0:
- return strings;
- case 1:
- strings.remove(indicesToRemove.get(0));
- return strings;
- default:
- int length = strings.size();
- ArrayList<String> result = new ArrayList<>(length - n);
- // Process indicesToRemove from the back; we know that it
- // contains indices in descending order.
- int j = n - 1;
- int idx = indicesToRemove.get(j);
- for (int i = 0; i < length; i++) {
- if (i == idx) {
- idx = (--j >= 0) ? indicesToRemove.get(j) : -1;
- } else {
- result.add(strings.get(i));
- }
- }
- return result;
- }
- }
-
private static boolean isSamePrefix(String a, String b) {
int as = a.lastIndexOf('/');
int bs = b.lastIndexOf('/');
@@ -684,9 +671,13 @@ public class DirCacheCheckout {
* Compares whether two pairs of ObjectId and FileMode are equal.
*
* @param id1
+ * id of first object
* @param mode1
+ * mode of first object
* @param id2
+ * id of second object
* @param mode2
+ * mode of second object
* @return <code>true</code> if FileModes and ObjectIds are equal.
* <code>false</code> otherwise
*/
@@ -712,6 +703,7 @@ public class DirCacheCheckout {
* @param f
* the file in the working tree
* @throws IOException
+ * if an IO error occurred
*/
void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
@@ -1229,13 +1221,17 @@ public class DirCacheCheckout {
if (!FileMode.TREE.equals(e.getFileMode())) {
builder.add(e);
}
+ if (existing != null) {
+ existing.add(path);
+ }
if (force) {
if (f == null || f.isModified(e, true, walk.getObjectReader())) {
kept.add(path);
- checkoutEntry(repo, e, walk.getObjectReader(), false,
+ checkout.checkout(e,
new CheckoutMetadata(walk.getEolStreamType(CHECKOUT_OP),
walk.getFilterCommand(
- Constants.ATTR_FILTER_TYPE_SMUDGE)), options);
+ Constants.ATTR_FILTER_TYPE_SMUDGE)),
+ walk.getObjectReader(), path);
}
}
}
@@ -1295,6 +1291,7 @@ public class DirCacheCheckout {
* {@link #failOnConflict} is false
*
* @throws CheckoutConflictException
+ * if a conflict occurred during merge checkout
*/
private void cleanUpConflicts() throws CheckoutConflictException {
// TODO: couldn't we delete unsaved worktree content here?
@@ -1308,13 +1305,16 @@ public class DirCacheCheckout {
}
/**
- * Checks whether the subtree starting at a given path differs between Index and
- * workingtree.
+ * Checks whether the subtree starting at a given path differs between Index
+ * and workingtree.
*
* @param path
+ * given subtree path
* @return true if the subtrees differ
* @throws CorruptObjectException
+ * if a corrupt object was found
* @throws IOException
+ * if an IO error occurred
*/
private boolean isModifiedSubtree_IndexWorkingtree(String path)
throws CorruptObjectException, IOException {
@@ -1355,15 +1355,18 @@ public class DirCacheCheckout {
}
/**
- * Checks whether the subtree starting at a given path differs between Index and
- * some tree.
+ * Checks whether the subtree starting at a given path differs between Index
+ * and some tree.
*
* @param path
+ * given path
* @param tree
* the tree to compare
* @return true if the subtrees differ
* @throws CorruptObjectException
+ * if a corrupt object was found
* @throws IOException
+ * if an IO error occurred
*/
private boolean isModifiedSubtree_IndexTree(String path, ObjectId tree)
throws CorruptObjectException, IOException {
@@ -1389,191 +1392,6 @@ public class DirCacheCheckout {
}
/**
- * Updates the file in the working tree with content and mode from an entry
- * in the index. The new content is first written to a new temporary file in
- * the same directory as the real file. Then that new file is renamed to the
- * final filename.
- *
- * <p>
- * <b>Note:</b> if the entry path on local file system exists as a non-empty
- * directory, and the target entry type is a link or file, the checkout will
- * fail with {@link java.io.IOException} since existing non-empty directory
- * cannot be renamed to file or link without deleting it recursively.
- * </p>
- *
- * @param repo
- * repository managing the destination work tree.
- * @param entry
- * the entry containing new mode and content
- * @param or
- * object reader to use for checkout
- * @throws java.io.IOException
- * @since 3.6
- * @deprecated since 5.1, use
- * {@link #checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean, CheckoutMetadata, WorkingTreeOptions)}
- * instead
- */
- @Deprecated
- public static void checkoutEntry(Repository repo, DirCacheEntry entry,
- ObjectReader or) throws IOException {
- checkoutEntry(repo, entry, or, false, null, null);
- }
-
-
- /**
- * Updates the file in the working tree with content and mode from an entry
- * in the index. The new content is first written to a new temporary file in
- * the same directory as the real file. Then that new file is renamed to the
- * final filename.
- *
- * <p>
- * <b>Note:</b> if the entry path on local file system exists as a file, it
- * will be deleted and if it exists as a directory, it will be deleted
- * recursively, independently if has any content.
- * </p>
- *
- * @param repo
- * repository managing the destination work tree.
- * @param entry
- * the entry containing new mode and content
- * @param or
- * object reader to use for checkout
- * @param deleteRecursive
- * true to recursively delete final path if it exists on the file
- * system
- * @param checkoutMetadata
- * containing
- * <ul>
- * <li>smudgeFilterCommand to be run for smudging the entry to be
- * checked out</li>
- * <li>eolStreamType used for stream conversion</li>
- * </ul>
- * @throws java.io.IOException
- * @since 4.2
- * @deprecated since 6.3, use
- * {@link #checkoutEntry(Repository, DirCacheEntry, ObjectReader, boolean, CheckoutMetadata, WorkingTreeOptions)}
- * instead
- */
- @Deprecated
- public static void checkoutEntry(Repository repo, DirCacheEntry entry,
- ObjectReader or, boolean deleteRecursive,
- CheckoutMetadata checkoutMetadata) throws IOException {
- checkoutEntry(repo, entry, or, deleteRecursive, checkoutMetadata, null);
- }
-
- /**
- * Updates the file in the working tree with content and mode from an entry
- * in the index. The new content is first written to a new temporary file in
- * the same directory as the real file. Then that new file is renamed to the
- * final filename.
- *
- * <p>
- * <b>Note:</b> if the entry path on local file system exists as a file, it
- * will be deleted and if it exists as a directory, it will be deleted
- * recursively, independently if has any content.
- * </p>
- *
- * @param repo
- * repository managing the destination work tree.
- * @param entry
- * the entry containing new mode and content
- * @param or
- * object reader to use for checkout
- * @param deleteRecursive
- * true to recursively delete final path if it exists on the file
- * system
- * @param checkoutMetadata
- * containing
- * <ul>
- * <li>smudgeFilterCommand to be run for smudging the entry to be
- * checked out</li>
- * <li>eolStreamType used for stream conversion</li>
- * </ul>
- * @param options
- * {@link WorkingTreeOptions} that are effective; if {@code null}
- * they are loaded from the repository config
- * @throws java.io.IOException
- * @since 6.3
- */
- public static void checkoutEntry(Repository repo, DirCacheEntry entry,
- ObjectReader or, boolean deleteRecursive,
- CheckoutMetadata checkoutMetadata, WorkingTreeOptions options)
- throws IOException {
- if (checkoutMetadata == null) {
- checkoutMetadata = CheckoutMetadata.EMPTY;
- }
- ObjectLoader ol = or.open(entry.getObjectId());
- File f = new File(repo.getWorkTree(), entry.getPathString());
- File parentDir = f.getParentFile();
- if (parentDir.isFile()) {
- FileUtils.delete(parentDir);
- }
- FileUtils.mkdirs(parentDir, true);
- FS fs = repo.getFS();
- WorkingTreeOptions opt = options != null ? options
- : repo.getConfig().get(WorkingTreeOptions.KEY);
- if (entry.getFileMode() == FileMode.SYMLINK
- && opt.getSymLinks() == SymLinks.TRUE) {
- byte[] bytes = ol.getBytes();
- String target = RawParseUtils.decode(bytes);
- if (deleteRecursive && f.isDirectory()) {
- FileUtils.delete(f, FileUtils.RECURSIVE);
- }
- fs.createSymLink(f, target);
- entry.setLength(bytes.length);
- entry.setLastModified(fs.lastModifiedInstant(f));
- return;
- }
-
- String name = f.getName();
- if (name.length() > 200) {
- name = name.substring(0, 200);
- }
- File tmpFile = File.createTempFile(
- "._" + name, null, parentDir); //$NON-NLS-1$
-
- getContent(repo, entry.getPathString(), checkoutMetadata, ol, opt,
- new FileOutputStream(tmpFile));
-
- // The entry needs to correspond to the on-disk filesize. If the content
- // was filtered (either by autocrlf handling or smudge filters) ask the
- // filesystem again for the length. Otherwise the objectloader knows the
- // size
- if (checkoutMetadata.eolStreamType == EolStreamType.DIRECT
- && checkoutMetadata.smudgeFilterCommand == null) {
- entry.setLength(ol.getSize());
- } else {
- entry.setLength(tmpFile.length());
- }
-
- if (opt.isFileMode() && fs.supportsExecute()) {
- if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) {
- if (!fs.canExecute(tmpFile))
- fs.setExecute(tmpFile, true);
- } else {
- if (fs.canExecute(tmpFile))
- fs.setExecute(tmpFile, false);
- }
- }
- try {
- if (deleteRecursive && f.isDirectory()) {
- FileUtils.delete(f, FileUtils.RECURSIVE);
- }
- FileUtils.rename(tmpFile, f, StandardCopyOption.ATOMIC_MOVE);
- } catch (IOException e) {
- throw new IOException(
- MessageFormat.format(JGitText.get().renameFileFailed,
- tmpFile.getPath(), f.getPath()),
- e);
- } finally {
- if (tmpFile.exists()) {
- FileUtils.delete(tmpFile);
- }
- }
- entry.setLastModified(fs.lastModifiedInstant(f));
- }
-
- /**
* Return filtered content for a specific object (blob). EOL handling and
* smudge-filter handling are applied in the same way as it would be done
* during a checkout.
@@ -1599,6 +1417,7 @@ public class DirCacheCheckout {
* the output stream the filtered content is written to. The
* caller is responsible to close the stream.
* @throws IOException
+ * if an IO error occurred
*
* @since 5.7
*/
@@ -1654,6 +1473,7 @@ public class DirCacheCheckout {
* the output stream the filtered content is written to. The
* caller is responsible to close the stream.
* @throws IOException
+ * if an IO error occurred
* @since 6.3
*/
public static void getContent(Repository repo, String path,
@@ -1697,6 +1517,8 @@ public class DirCacheCheckout {
filterProcessBuilder.directory(repo.getWorkTree());
filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
repo.getDirectory().getAbsolutePath());
+ filterProcessBuilder.environment().put(Constants.GIT_COMMON_DIR_KEY,
+ repo.getCommonDirectory().getAbsolutePath());
ExecutionResult result;
int rc;
try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
index 8c342e267d..b1f4e7db21 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
@@ -37,7 +37,6 @@ import org.eclipse.jgit.util.Paths;
* 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 org.eclipse.jgit.dircache.DirCacheBuilder}.
- * <p>
*
* @see DirCacheBuilder
*/
@@ -80,7 +79,6 @@ public class DirCacheEditor extends BaseDirCacheEditor {
edits.add(edit);
}
- /** {@inheritDoc} */
@Override
public boolean commit() throws IOException {
if (edits.isEmpty()) {
@@ -92,7 +90,6 @@ public class DirCacheEditor extends BaseDirCacheEditor {
return super.commit();
}
- /** {@inheritDoc} */
@Override
public void finish() {
if (!edits.isEmpty()) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
index 67edf50f44..5a22938694 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
@@ -396,33 +396,12 @@ public class DirCacheEntry {
* 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.
- * @deprecated use {@link #mightBeRacilyClean(Instant)} instead
- */
- @Deprecated
- public final boolean mightBeRacilyClean(int smudge_s, int smudge_ns) {
- return mightBeRacilyClean(Instant.ofEpochSecond(smudge_s, smudge_ns));
- }
-
- /**
- * Is it possible for this entry to be accidentally assumed clean?
- * <p>
- * The "racy git" problem happens when a work file can be updated faster
- * than the filesystem records file modification timestamps. It is possible
- * for an application to edit a work file, update the index, then edit it
- * again before the filesystem will give the work file a new modification
- * timestamp. This method tests to see if file was written out at the same
- * time as the index.
- *
* @param smudge
* index's last modified time.
* @return true if extra careful checks should be used.
* @since 5.1.9
*/
+ @SuppressWarnings("JavaInstantGetSecondsGetNano")
public final boolean mightBeRacilyClean(Instant smudge) {
// If the index has a modification time then it came from disk
// and was not generated from scratch in memory. In such cases
@@ -652,22 +631,6 @@ public class DirCacheEntry {
}
/**
- * Get the cached last modification date of this file, in milliseconds.
- * <p>
- * One of the indicators that the file has been modified by an application
- * changing the working tree is if the last modification time for the file
- * differs from the time stored in this entry.
- *
- * @return last modification time of this file, in milliseconds since the
- * Java epoch (midnight Jan 1, 1970 UTC).
- * @deprecated use {@link #getLastModifiedInstant()} instead
- */
- @Deprecated
- public long getLastModified() {
- return decodeTS(P_MTIME);
- }
-
- /**
* Get the cached last modification date of this file.
* <p>
* One of the indicators that the file has been modified by an application
@@ -682,18 +645,6 @@ public class DirCacheEntry {
}
/**
- * Set the cached last modification date of this file, using milliseconds.
- *
- * @param when
- * new cached modification date of the file, in milliseconds.
- * @deprecated use {@link #setLastModified(Instant)} instead
- */
- @Deprecated
- public void setLastModified(long when) {
- encodeTS(P_MTIME, when);
- }
-
- /**
* Set the cached last modification date of this file.
*
* @param when
@@ -864,6 +815,8 @@ public class DirCacheEntry {
}
/**
+ * Whether the entry contains extended flags
+ *
* @return true if the entry contains extended flags.
*/
boolean isExtended() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
index aed1c341f2..9cc34a0f11 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java
@@ -104,7 +104,6 @@ public class DirCacheIterator extends AbstractTreeIterator {
parseEntry();
}
- /** {@inheritDoc} */
@Override
public AbstractTreeIterator createSubtreeIterator(ObjectReader reader)
throws IncorrectObjectTypeException, IOException {
@@ -114,7 +113,6 @@ public class DirCacheIterator extends AbstractTreeIterator {
return new DirCacheIterator(this, currentSubtree);
}
- /** {@inheritDoc} */
@Override
public EmptyTreeIterator createEmptyTreeIterator() {
final byte[] n = new byte[Math.max(pathLen + 1, DEFAULT_PATH_SIZE)];
@@ -123,7 +121,6 @@ public class DirCacheIterator extends AbstractTreeIterator {
return new EmptyTreeIterator(this, n, pathLen + 1);
}
- /** {@inheritDoc} */
@Override
public boolean hasId() {
if (currentSubtree != null)
@@ -131,7 +128,6 @@ public class DirCacheIterator extends AbstractTreeIterator {
return currentEntry != null;
}
- /** {@inheritDoc} */
@Override
public byte[] idBuffer() {
if (currentSubtree != null)
@@ -141,7 +137,6 @@ public class DirCacheIterator extends AbstractTreeIterator {
return zeroid;
}
- /** {@inheritDoc} */
@Override
public int idOffset() {
if (currentSubtree != null)
@@ -151,7 +146,6 @@ public class DirCacheIterator extends AbstractTreeIterator {
return 0;
}
- /** {@inheritDoc} */
@Override
public void reset() {
if (!first()) {
@@ -164,19 +158,16 @@ public class DirCacheIterator extends AbstractTreeIterator {
}
}
- /** {@inheritDoc} */
@Override
public boolean first() {
return ptr == treeStart;
}
- /** {@inheritDoc} */
@Override
public boolean eof() {
return ptr == treeEnd;
}
- /** {@inheritDoc} */
@Override
public void next(int delta) {
while (--delta >= 0) {
@@ -190,7 +181,6 @@ public class DirCacheIterator extends AbstractTreeIterator {
}
}
- /** {@inheritDoc} */
@Override
public void back(int delta) {
while (--delta >= 0) {
@@ -271,6 +261,7 @@ public class DirCacheIterator extends AbstractTreeIterator {
* @return {@link org.eclipse.jgit.attributes.AttributesNode} for the
* current entry.
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.7
*/
public AttributesNode getEntryAttributesNode(ObjectReader reader)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
index e0c1e93918..2561ae999b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheTree.java
@@ -534,7 +534,6 @@ public class DirCacheTree {
return -1;
}
- /** {@inheritDoc} */
@Override
public String toString() {
return getNameString();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/LargeObjectException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/LargeObjectException.java
index 4a535619d7..40aa1c3b7c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/LargeObjectException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/LargeObjectException.java
@@ -84,7 +84,6 @@ public class LargeObjectException extends RuntimeException {
objectId = id.copy();
}
- /** {@inheritDoc} */
@Override
public String getMessage() {
return MessageFormat.format(JGitText.get().largeObjectException,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java
index 1fd80867b9..38982fdf23 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackInvalidException.java
@@ -23,18 +23,6 @@ public class PackInvalidException extends IOException {
private static final long serialVersionUID = 1L;
/**
- * Construct a pack invalid error.
- *
- * @param path
- * path of the invalid pack file.
- * @deprecated Use {@link #PackInvalidException(File, Throwable)}.
- */
- @Deprecated
- public PackInvalidException(File path) {
- this(path, null);
- }
-
- /**
* Construct a pack invalid error with cause.
*
* @param path
@@ -48,18 +36,6 @@ public class PackInvalidException extends IOException {
}
/**
- * Construct a pack invalid error.
- *
- * @param path
- * path of the invalid pack file.
- * @deprecated Use {@link #PackInvalidException(String, Throwable)}.
- */
- @Deprecated
- public PackInvalidException(String path) {
- this(path, null);
- }
-
- /**
* Construct a pack invalid error with cause.
*
* @param path
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java
index 7a2c70de79..e630f529e3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/PackMismatchException.java
@@ -51,9 +51,4 @@ public class PackMismatchException extends IOException {
public boolean isPermanent() {
return permanent;
}
-
- @Override
- public String toString() {
- return super.toString() + ", permanent: " + permanent; //$NON-NLS-1$
- }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevisionSyntaxException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevisionSyntaxException.java
index 084d67c02b..293b4c6818 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevisionSyntaxException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/RevisionSyntaxException.java
@@ -44,9 +44,13 @@ public class RevisionSyntaxException extends IllegalArgumentException {
this.revstr = revstr;
}
- /** {@inheritDoc} */
- @Override
- public String toString() {
- return super.toString() + ":" + revstr; //$NON-NLS-1$
+ /**
+ * Get the problematic revision string
+ *
+ * @return the problematic revision string
+ * @since 6.8
+ */
+ public String getRevstr() {
+ return revstr;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedEvent.java
index e6342542d0..0c2aa46aa0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedEvent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ConfigChangedEvent.java
@@ -14,13 +14,11 @@ package org.eclipse.jgit.events;
* Describes a change to one or more keys in the configuration.
*/
public class ConfigChangedEvent extends RepositoryEvent<ConfigChangedListener> {
- /** {@inheritDoc} */
@Override
public Class<ConfigChangedListener> getListenerType() {
return ConfigChangedListener.class;
}
- /** {@inheritDoc} */
@Override
public void dispatch(ConfigChangedListener listener) {
listener.onConfigChanged(this);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java
index f5a6d64b16..72d004bc47 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/IndexChangedEvent.java
@@ -29,6 +29,8 @@ public class IndexChangedEvent extends RepositoryEvent<IndexChangedListener> {
}
/**
+ * Whether the index was changed by the same JGit process
+ *
* @return {@code true} if the index was changed by the same JGit process
* @since 5.0
*/
@@ -36,13 +38,11 @@ public class IndexChangedEvent extends RepositoryEvent<IndexChangedListener> {
return internal;
}
- /** {@inheritDoc} */
@Override
public Class<IndexChangedListener> getListenerType() {
return IndexChangedListener.class;
}
- /** {@inheritDoc} */
@Override
public void dispatch(IndexChangedListener listener) {
listener.onIndexChanged(this);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java
index 44897605db..94d49fd93e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerHandle.java
@@ -35,7 +35,6 @@ public class ListenerHandle {
parent.remove(this);
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java
index 476c37c1c3..92a227750f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java
@@ -71,6 +71,8 @@ public class ListenerList {
/**
* Add a listener to the list.
*
+ * @param <T>
+ * type of {@code RepositoryListener}
* @param type
* type of listener being registered.
* @param listener
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedEvent.java
index 6f1e8d5d66..9bd1ef0653 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedEvent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/RefsChangedEvent.java
@@ -14,13 +14,11 @@ package org.eclipse.jgit.events;
* Describes a change to one or more references of a repository.
*/
public class RefsChangedEvent extends RepositoryEvent<RefsChangedListener> {
- /** {@inheritDoc} */
@Override
public Class<RefsChangedListener> getListenerType() {
return RefsChangedListener.class;
}
- /** {@inheritDoc} */
@Override
public void dispatch(RefsChangedListener listener) {
listener.onRefsChanged(this);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java
index 81f2e73de2..288074e15f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/RepositoryEvent.java
@@ -61,7 +61,6 @@ public abstract class RepositoryEvent<T extends RepositoryListener> {
*/
public abstract void dispatch(T listener);
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java
index f9888eae34..c3275153ea 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java
@@ -87,13 +87,11 @@ public class WorkingTreeModifiedEvent
return result;
}
- /** {@inheritDoc} */
@Override
public Class<WorkingTreeModifiedListener> getListenerType() {
return WorkingTreeModifiedListener.class;
}
- /** {@inheritDoc} */
@Override
public void dispatch(WorkingTreeModifiedListener listener) {
listener.onWorkingTreeModified(this);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java
index d54bf65a27..8668ea9c0e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java
@@ -45,7 +45,6 @@ abstract class AbstractHead implements Head {
this.newHeads = newHeads;
}
- /** {@inheritDoc} */
@Override
public List<Head> getNextHeads(char c) {
if (matches(c)) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java
index faf4ee66c9..92fee62188 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/CharacterHead.java
@@ -24,13 +24,11 @@ final class CharacterHead extends AbstractHead {
this.expectedCharacter = expectedCharacter;
}
- /** {@inheritDoc} */
@Override
protected final boolean matches(char c) {
return c == expectedCharacter;
}
- /** {@inheritDoc} */
@Override
public String toString() {
return String.valueOf(expectedCharacter);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java
index 5ffcafc41e..3b240c3b74 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/GroupHead.java
@@ -96,7 +96,6 @@ final class GroupHead extends AbstractHead {
}
}
- /** {@inheritDoc} */
@Override
protected final boolean matches(char c) {
for (CharacterPattern pattern : characterClasses) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java
index 6aac3faab3..d4cc850a12 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/LastHead.java
@@ -22,7 +22,6 @@ final class LastHead implements Head {
// defined because of javadoc and visibility modifier.
}
- /** {@inheritDoc} */
@Override
public List<Head> getNextHeads(char c) {
return FileNameMatcher.EMPTY_HEAD_LIST;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java
index 14b6038c44..6b4526f825 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/RestrictedWildCardHead.java
@@ -18,13 +18,11 @@ final class RestrictedWildCardHead extends AbstractHead {
this.excludedCharacter = excludedCharacter;
}
- /** {@inheritDoc} */
@Override
protected final boolean matches(char c) {
return c != excludedCharacter;
}
- /** {@inheritDoc} */
@Override
public String toString() {
return isStar() ? "*" : "?"; //$NON-NLS-1$ //$NON-NLS-2$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/WildCardHead.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/WildCardHead.java
index 3665a70476..62b6f42c52 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/WildCardHead.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/WildCardHead.java
@@ -15,7 +15,6 @@ final class WildCardHead extends AbstractHead {
super(star);
}
- /** {@inheritDoc} */
@Override
protected final boolean matches(char c) {
return true;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java
index f63cc6d644..e511a68d2e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/BareSuperprojectWriter.java
@@ -107,7 +107,7 @@ class BareSuperprojectWriter {
PersonIdent author, RemoteReader callback,
BareWriterConfig config,
List<ExtraContent> extraContents) {
- assert (repo.isBare());
+ assert repo.isBare();
this.repo = repo;
this.targetUri = targetUri;
this.targetBranch = targetBranch;
@@ -156,6 +156,9 @@ class BareSuperprojectWriter {
ObjectId objectId;
if (ObjectId.isId(proj.getRevision())) {
objectId = ObjectId.fromString(proj.getRevision());
+ if (config.recordRemoteBranch && proj.getUpstream() != null) {
+ cfg.setString("submodule", name, "ref", proj.getUpstream()); //$NON-NLS-1$//$NON-NLS-2$
+ }
} else {
objectId = callback.sha1(url, proj.getRevision());
if (objectId == null && !config.ignoreRemoteFailures) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
index aa69a05112..58b4d3dc56 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
@@ -75,7 +75,9 @@ public class ManifestParser extends DefaultHandler {
* The relative path to the file to read
* @return the {@code InputStream} of the file.
* @throws GitAPIException
+ * a JGit API exception
* @throws IOException
+ * if an IO error occurred
*/
public InputStream readIncludeFile(String path)
throws GitAPIException, IOException;
@@ -134,12 +136,23 @@ public class ManifestParser extends DefaultHandler {
* @param inputStream
* a {@link java.io.InputStream} object.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void read(InputStream inputStream) throws IOException {
xmlInRead++;
final XMLReader xr;
try {
- xr = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
+ SAXParserFactory spf = SAXParserFactory.newInstance();
+ spf.setFeature(
+ "http://xml.org/sax/features/external-general-entities", //$NON-NLS-1$
+ false);
+ spf.setFeature(
+ "http://xml.org/sax/features/external-parameter-entities", //$NON-NLS-1$
+ false);
+ spf.setFeature(
+ "http://apache.org/xml/features/disallow-doctype-decl", //$NON-NLS-1$
+ true);
+ xr = spf.newSAXParser().getXMLReader();
} catch (SAXException | ParserConfigurationException e) {
throw new IOException(JGitText.get().noXMLParserAvailable, e);
}
@@ -151,7 +164,6 @@ public class ManifestParser extends DefaultHandler {
}
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public void startElement(
@@ -174,6 +186,10 @@ public class ManifestParser extends DefaultHandler {
attributes.getValue("groups"));
currentProject
.setRecommendShallow(attributes.getValue("clone-depth"));
+ currentProject
+ .setUpstream(attributes.getValue("upstream"));
+ currentProject
+ .setDestBranch(attributes.getValue("dest-branch"));
break;
case "remote":
String alias = attributes.getValue("alias");
@@ -239,7 +255,6 @@ public class ManifestParser extends DefaultHandler {
}
}
- /** {@inheritDoc} */
@Override
public void endElement(
String uri,
@@ -251,7 +266,6 @@ public class ManifestParser extends DefaultHandler {
}
}
- /** {@inheritDoc} */
@Override
public void endDocument() throws SAXException {
xmlInRead--;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RegularSuperprojectWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RegularSuperprojectWriter.java
index afab9943a7..7632b367c1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RegularSuperprojectWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RegularSuperprojectWriter.java
@@ -64,8 +64,8 @@ class RegularSuperprojectWriter {
private void addSubmodule(String name, String url, String path,
String revision, List<CopyFile> copyfiles, List<LinkFile> linkfiles,
Git git) throws GitAPIException, IOException {
- assert (!repo.isBare());
- assert (git != null);
+ assert !repo.isBare();
+ assert git != null;
if (!linkfiles.isEmpty()) {
throw new UnsupportedOperationException(
JGitText.get().nonBareLinkFilesNotSupported);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
index 6e943e5d36..be77fca459 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -105,34 +105,12 @@ public class RepoCommand extends GitCommand<RevCommit> {
* @return the sha1 of the remote repository, or null if the ref does
* not exist.
* @throws GitAPIException
+ * a JGit API exception
*/
@Nullable
public ObjectId sha1(String uri, String ref) throws GitAPIException;
/**
- * Read a file from a remote repository.
- *
- * @param uri
- * The URI of the remote repository
- * @param ref
- * The ref (branch/tag/etc.) to read
- * @param path
- * The relative path (inside the repo) to the file to read
- * @return the file content.
- * @throws GitAPIException
- * @throws IOException
- * @since 3.5
- *
- * @deprecated Use {@link #readFileWithMode(String, String, String)}
- * instead
- */
- @Deprecated
- public default byte[] readFile(String uri, String ref, String path)
- throws GitAPIException, IOException {
- return readFileWithMode(uri, ref, path).getContents();
- }
-
- /**
* Read contents and mode (i.e. permissions) of the file from a remote
* repository.
*
@@ -197,6 +175,8 @@ public class RepoCommand extends GitCommand<RevCommit> {
}
/**
+ * Get file mode
+ *
* @return Git file mode for this file (e.g. executable or regular)
*/
@NonNull
@@ -249,7 +229,8 @@ public class RepoCommand extends GitCommand<RevCommit> {
@SuppressWarnings("serial")
static class ManifestErrorException extends GitAPIException {
ManifestErrorException(Throwable cause) {
- super(RepoText.get().invalidManifest, cause);
+ super(RepoText.get().invalidManifest + " " + cause.getMessage(), //$NON-NLS-1$
+ cause);
}
}
@@ -528,7 +509,6 @@ public class RepoCommand extends GitCommand<RevCommit> {
return this;
}
- /** {@inheritDoc} */
@Override
public RevCommit call() throws GitAPIException {
checkCallable();
@@ -610,6 +590,7 @@ public class RepoCommand extends GitCommand<RevCommit> {
p.setUrl(proj.getUrl());
p.addCopyFiles(proj.getCopyFiles());
p.addLinkFiles(proj.getLinkFiles());
+ p.setUpstream(proj.getUpstream());
ret.add(p);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
index c5ead09523..2630da34e0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
@@ -38,6 +38,8 @@ public class RepoProject implements Comparable<RepoProject> {
private final Set<String> groups;
private final List<CopyFile> copyfiles;
private final List<LinkFile> linkfiles;
+ private String upstream;
+ private String destBranch;
private String recommendShallow;
private String url;
private String defaultRevision;
@@ -93,6 +95,7 @@ public class RepoProject implements Comparable<RepoProject> {
* Do the copy file action.
*
* @throws IOException
+ * if an IO error occurred
*/
public void copy() throws IOException {
File srcFile = new File(repo.getWorkTree(),
@@ -180,7 +183,7 @@ public class RepoProject implements Comparable<RepoProject> {
*/
public RepoProject(String name, String path, String revision,
String remote, String groupsParam) {
- this(name, path, revision, remote, new HashSet<String>(), null);
+ this(name, path, revision, remote, new HashSet<>(), null);
if (groupsParam != null && groupsParam.length() > 0)
this.setGroups(groupsParam);
}
@@ -388,6 +391,57 @@ public class RepoProject implements Comparable<RepoProject> {
this.linkfiles.clear();
}
+ /**
+ * Return the upstream attribute of the project
+ *
+ * @return the upstream value if present, null otherwise.
+ *
+ * @since 6.10
+ */
+ public String getUpstream() {
+ return this.upstream;
+ }
+
+ /**
+ * Return the dest-branch attribute of the project
+ *
+ * @return the dest-branch value if present, null otherwise.
+ *
+ * @since 6.10
+ */
+ public String getDestBranch() {
+ return this.destBranch;
+ }
+
+ /**
+ * Set the upstream attribute of the project
+ *
+ * Name of the git ref in which a sha1 can be found, when the revision is a
+ * sha1.
+ *
+ * @param upstream
+ * value of the attribute in the manifest
+ *
+ * @since 6.10
+ */
+ public void setUpstream(String upstream) {
+ this.upstream = upstream;
+ }
+
+ /**
+ * Set the dest-branch attribute of the project
+ *
+ * Name of a Git branch.
+ *
+ * @param destBranch
+ * value of the attribute in the manifest
+ *
+ * @since 6.10
+ */
+ public void setDestBranch(String destBranch) {
+ this.destBranch = destBranch;
+ }
+
private String getPathWithSlash() {
if (path.endsWith("/")) { //$NON-NLS-1$
return path;
@@ -418,7 +472,6 @@ public class RepoProject implements Comparable<RepoProject> {
return thatPath.startsWith(getPathWithSlash());
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object o) {
if (o instanceof RepoProject) {
@@ -428,13 +481,11 @@ public class RepoProject implements Comparable<RepoProject> {
return false;
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
return this.getPathWithSlash().hashCode();
}
- /** {@inheritDoc} */
@Override
public int compareTo(RepoProject that) {
return this.getPathWithSlash().compareTo(that.getPathWithSlash());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java
index 68c57663e3..a012d94c32 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java
@@ -16,6 +16,7 @@ import org.eclipse.jgit.nls.TranslationBundle;
/**
* Translation bundle for repo command
*/
+@SuppressWarnings("MissingSummary")
public class RepoText extends TranslationBundle {
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/CommitMsgHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/CommitMsgHook.java
index 023aef1c1f..17fdd7e7cb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/CommitMsgHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/CommitMsgHook.java
@@ -71,7 +71,6 @@ public class CommitMsgHook extends GitHook<String> {
super(repo, outputStream, errorStream);
}
- /** {@inheritDoc} */
@Override
public String call() throws IOException, AbortedByHookException {
if (commitMessage == null) {
@@ -94,7 +93,6 @@ public class CommitMsgHook extends GitHook<String> {
return getCommitEditMessageFilePath() != null && commitMessage != null;
}
- /** {@inheritDoc} */
@Override
public String getHookName() {
return NAME;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java
index b9dafcca31..3988ee06ca 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java
@@ -61,14 +61,12 @@ public class PostCommitHook extends GitHook<Void> {
super(repo, outputStream, errorStream);
}
- /** {@inheritDoc} */
@Override
public Void call() throws IOException, AbortedByHookException {
doRun();
return null;
}
- /** {@inheritDoc} */
@Override
public String getHookName() {
return NAME;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PreCommitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PreCommitHook.java
index 321f476620..b12ea83b99 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PreCommitHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PreCommitHook.java
@@ -60,14 +60,12 @@ public class PreCommitHook extends GitHook<Void> {
super(repo, outputStream, errorStream);
}
- /** {@inheritDoc} */
@Override
public Void call() throws IOException, AbortedByHookException {
doRun();
return null;
}
- /** {@inheritDoc} */
@Override
public String getHookName() {
return NAME;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
index 43dbc37f4f..e36312b3da 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
@@ -74,13 +74,11 @@ public class PrePushHook extends GitHook<String> {
super(repo, outputStream, errorStream);
}
- /** {@inheritDoc} */
@Override
protected String getStdinArgs() {
return refs;
}
- /** {@inheritDoc} */
@Override
public String call() throws IOException, AbortedByHookException {
if (canRun()) {
@@ -96,7 +94,6 @@ public class PrePushHook extends GitHook<String> {
return true;
}
- /** {@inheritDoc} */
@Override
public String getHookName() {
return NAME;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
index 9dd565ff0a..b041729e28 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
@@ -208,7 +208,6 @@ public class FastIgnoreRule {
return matcher == NO_MATCH;
}
- /** {@inheritDoc} */
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@@ -221,7 +220,6 @@ public class FastIgnoreRule {
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
final int prime = 31;
@@ -232,7 +230,6 @@ public class FastIgnoreRule {
return result;
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object obj) {
if (this == obj)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
index 4e7f126a60..33dceb0717 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
@@ -190,7 +190,6 @@ public class IgnoreNode {
return null;
}
- /** {@inheritDoc} */
@Override
public String toString() {
return rules.toString();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java
index 0737ed8878..a3f365ea0e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java
@@ -33,19 +33,16 @@ public abstract class AbstractMatcher implements IMatcher {
this.dirOnly = dirOnly;
}
- /** {@inheritDoc} */
@Override
public String toString() {
return pattern;
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
return pattern.hashCode();
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object obj) {
if (this == obj)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java
index 73e2ad3977..84376be53e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java
@@ -22,7 +22,6 @@ public class LeadingAsteriskMatcher extends NameMatcher {
"Pattern must have leading asterisk: " + pattern); //$NON-NLS-1$
}
- /** {@inheritDoc} */
@Override
public boolean matches(String segment, int startIncl, int endExcl) {
// faster local access, same as in string.indexOf()
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java
index 39fa74f6ea..4f8e149b4b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java
@@ -38,7 +38,6 @@ public class NameMatcher extends AbstractMatcher {
}
}
- /** {@inheritDoc} */
@Override
public boolean matches(String path, boolean assumeDirectory,
boolean pathMatch) {
@@ -88,7 +87,6 @@ public class NameMatcher extends AbstractMatcher {
return false;
}
- /** {@inheritDoc} */
@Override
public boolean matches(String segment, int startIncl, int endExcl) {
// faster local access, same as in string.indexOf()
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
index ba77b3dd87..a40568de08 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
@@ -91,6 +91,7 @@ public class PathMatcher extends AbstractMatcher {
* a boolean.
* @return never null
* @throws org.eclipse.jgit.errors.InvalidPatternException
+ * if pattern is invalid
*/
public static IMatcher createPathMatcher(String pattern,
Character pathSeparator, boolean dirOnly)
@@ -149,7 +150,6 @@ public class PathMatcher extends AbstractMatcher {
}
}
- /** {@inheritDoc} */
@Override
public boolean matches(String path, boolean assumeDirectory,
boolean pathMatch) {
@@ -192,7 +192,6 @@ public class PathMatcher extends AbstractMatcher {
return false;
}
- /** {@inheritDoc} */
@Override
public boolean matches(String segment, int startIncl, int endExcl) {
throw new UnsupportedOperationException(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
index fbce08adf4..3305a74af1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
@@ -232,16 +232,19 @@ public class Strings {
.compile("\\[\\[[.=]\\w+[.=]\\]\\]"); //$NON-NLS-1$
/**
- * Conversion from glob to Java regex following two sources: <li>
- * http://man7.org/linux/man-pages/man7/glob.7.html <li>
- * org.eclipse.jgit.fnmatch.FileNameMatcher.java Seems that there are
+ * Conversion from glob to Java regex following two sources:
+ * <ul>
+ * <li>http://man7.org/linux/man-pages/man7/glob.7.html
+ * <li>org.eclipse.jgit.fnmatch.FileNameMatcher.java Seems that there are
* various ways to define what "glob" can be.
+ * </ul>
*
* @param pattern
* non null pattern
*
* @return Java regex pattern corresponding to given glob pattern
* @throws InvalidPatternException
+ * if pattern is invalid
*/
static Pattern convertGlob(String pattern) throws InvalidPatternException {
if (UNSUPPORTED.matcher(pattern).find())
@@ -419,6 +422,7 @@ public class Strings {
/**
* @param buffer
+ * buffer
* @return zero of the buffer is empty, otherwise the last character from
* buffer
*/
@@ -427,10 +431,13 @@ public class Strings {
}
/**
+ * Lookahead next character after given index in pattern
+ *
* @param pattern
+ * the pattern
* @param i
* current pointer in the pattern
- * @return zero of the index is out of range, otherwise the next character
+ * @return zero if the index is out of range, otherwise the next character
* from given position
*/
private static char lookAhead(String pattern, int i) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java
index 7fa076b855..1f1c223270 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java
@@ -22,7 +22,6 @@ public class TrailingAsteriskMatcher extends NameMatcher {
"Pattern must have trailing asterisk: " + pattern); //$NON-NLS-1$
}
- /** {@inheritDoc} */
@Override
public boolean matches(String segment, int startIncl, int endExcl) {
// faster local access, same as in string.indexOf()
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java
index 76a88641c9..d976f6ea5b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java
@@ -31,7 +31,6 @@ public class WildCardMatcher extends NameMatcher {
p = convertGlob(subPattern);
}
- /** {@inheritDoc} */
@Override
public boolean matches(String segment, int startIncl, int endExcl) {
return p.matcher(segment.substring(startIncl, endExcl)).matches();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java
index 1baa9abf19..606b585cdf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java
@@ -26,7 +26,6 @@ public final class WildMatcher extends AbstractMatcher {
super(WILDMATCH, dirOnly);
}
- /** {@inheritDoc} */
@Override
public final boolean matches(String path, boolean assumeDirectory,
boolean pathMatch) {
@@ -34,7 +33,6 @@ public final class WildMatcher extends AbstractMatcher {
|| (!pathMatch && isSubdirectory(path));
}
- /** {@inheritDoc} */
@Override
public final boolean matches(String segment, int startIncl, int endExcl) {
return true;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index 518e0b7d9b..8928f47290 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -17,6 +17,7 @@ import org.eclipse.jgit.nls.TranslationBundle;
/**
* Translation bundle for JGit core
*/
+@SuppressWarnings("MissingSummary")
public class JGitText extends TranslationBundle {
/**
@@ -46,6 +47,8 @@ public class JGitText extends TranslationBundle {
/***/ public String applyBinaryOidTooShort;
/***/ public String applyBinaryPatchTypeNotSupported;
/***/ public String applyBinaryResultOidWrong;
+ /***/ public String applyPatchDestInvalid;
+ /***/ public String applyPatchSourceInvalid;
/***/ public String applyPatchWithoutSourceOnAlreadyExistingSource;
/***/ public String applyPatchWithCreationOverAlreadyExistingDestination;
/***/ public String applyPatchWithSourceOnNonExistentSource;
@@ -93,6 +96,7 @@ public class JGitText extends TranslationBundle {
/***/ public String binaryHunkMissingNewline;
/***/ public String bitmapMissingObject;
/***/ public String bitmapsMustBePrepared;
+ /***/ public String bitmapUseNoopNoListener;
/***/ public String blameNotCommittedYet;
/***/ public String blockLimitNotMultipleOfBlockSize;
/***/ public String blockLimitNotPositive;
@@ -102,6 +106,7 @@ public class JGitText extends TranslationBundle {
/***/ public String buildingBitmaps;
/***/ public String cachedPacksPreventsIndexCreation;
/***/ public String cachedPacksPreventsListingObjects;
+ /***/ public String cacheRegionAllOrNoneNull;
/***/ public String cannotAccessLastModifiedForSafeDeletion;
/***/ public String cannotBeCombined;
/***/ public String cannotBeRecursiveWhenTreesAreIncluded;
@@ -185,6 +190,7 @@ public class JGitText extends TranslationBundle {
/***/ public String commitGraphChunkRepeated;
/***/ public String commitGraphChunkUnknown;
/***/ public String commitGraphFileIsTooLargeForJgit;
+ /***/ public String commitGraphUnexpectedSize;
/***/ public String commitGraphWritingCancelled;
/***/ public String commitMessageNotSpecified;
/***/ public String commitOnRepoWithoutHEADCurrentlyNotSupported;
@@ -192,6 +198,7 @@ public class JGitText extends TranslationBundle {
/***/ public String commitsHaveAlreadyBeenMarkedAsStart;
/***/ public String compressingObjects;
/***/ public String computingCommitGeneration;
+ /***/ public String computingPathBloomFilters;
/***/ public String configSubsectionContainsNewline;
/***/ public String configSubsectionContainsNullByte;
/***/ public String configValueContainsNullByte;
@@ -252,6 +259,7 @@ public class JGitText extends TranslationBundle {
/***/ public String corruptObjectTruncatedInName;
/***/ public String corruptObjectTruncatedInObjectId;
/***/ public String corruptObjectZeroId;
+ /***/ public String corruptReverseIndexChecksumIncorrect;
/***/ public String corruptPack;
/***/ public String corruptUseCnt;
/***/ public String couldNotFindTabInLine;
@@ -284,7 +292,9 @@ public class JGitText extends TranslationBundle {
/***/ public String deletedOrphanInPackDir;
/***/ public String deleteRequiresZeroNewId;
/***/ public String deleteTagUnexpectedResult;
+ /***/ public String deletingBranches;
/***/ public String deletingNotSupported;
+ /***/ public String deprecatedTrustFolderStat;
/***/ public String depthMustBeAt1;
/***/ public String depthWithUnshallow;
/***/ public String destinationIsNotAWildcard;
@@ -305,6 +315,9 @@ public class JGitText extends TranslationBundle {
/***/ public String downloadCancelled;
/***/ public String downloadCancelledDuringIndexing;
/***/ public String duplicateAdvertisementsOf;
+ /***/ public String duplicateCacheTablesGiven;
+ /***/ public String duplicatePackExtensionsForCacheTables;
+ /***/ public String duplicatePackExtensionsSet;
/***/ public String duplicateRef;
/***/ public String duplicateRefAttribute;
/***/ public String duplicateRemoteRefUpdateIsIllegal;
@@ -414,6 +427,7 @@ public class JGitText extends TranslationBundle {
/***/ public String initFailedNonBareRepoSameDirs;
/***/ public String inMemoryBufferLimitExceeded;
/***/ public String inputDidntMatchLength;
+ /***/ public String inputStreamClosed;
/***/ public String inputStreamMustSupportMark;
/***/ public String integerValueNotInRange;
/***/ public String integerValueNotInRangeSubSection;
@@ -479,6 +493,7 @@ public class JGitText extends TranslationBundle {
/***/ public String invalidTimeUnitValue2;
/***/ public String invalidTimeUnitValue3;
/***/ public String invalidTreeZeroLengthName;
+ /***/ public String invalidTrustStat;
/***/ public String invalidURL;
/***/ public String invalidWildcards;
/***/ public String invalidRefSpec;
@@ -511,6 +526,8 @@ public class JGitText extends TranslationBundle {
/***/ public String logLargerFiletimeDiff;
/***/ public String logSmallerFiletime;
/***/ public String logXDGConfigHomeInvalid;
+
+ /***/ public String logXDGCacheHomeInvalid;
/***/ public String looseObjectHandleIsStale;
/***/ public String maxCountMustBeNonNegative;
/***/ public String mergeConflictOnNonNoteEntries;
@@ -523,6 +540,9 @@ public class JGitText extends TranslationBundle {
/***/ public String mergeToolNotGivenError;
/***/ public String mergeToolNullError;
/***/ public String messageAndTaggerNotAllowedInUnannotatedTags;
+ /***/ public String midxChunkNeeded;
+ /***/ public String midxChunkRepeated;
+ /***/ public String midxChunkUnknown;
/***/ public String minutesAgo;
/***/ public String mismatchOffset;
/***/ public String mismatchCRC;
@@ -543,6 +563,10 @@ public class JGitText extends TranslationBundle {
/***/ public String month;
/***/ public String months;
/***/ public String monthsAgo;
+ /***/ public String multiPackIndexFileIsTooLargeForJgit;
+ /***/ public String multiPackIndexPackCountMismatch;
+ /***/ public String multiPackIndexUnexpectedSize;
+ /***/ public String multiPackIndexWritingCancelled;
/***/ public String multipleMergeBasesFor;
/***/ public String nameMustNotBeNullOrEmpty;
/***/ public String need2Arguments;
@@ -558,6 +582,8 @@ public class JGitText extends TranslationBundle {
/***/ public String noMergeHeadSpecified;
/***/ public String nonBareLinkFilesNotSupported;
/***/ public String nonCommitToHeads;
+ /***/ public String noPackExtConfigurationGiven;
+ /***/ public String noPackExtGivenForConfiguration;
/***/ public String noPathAttributesFound;
/***/ public String noSuchRef;
/***/ public String noSuchRefKnown;
@@ -567,6 +593,7 @@ public class JGitText extends TranslationBundle {
/***/ public String notACommitGraph;
/***/ public String notADIRCFile;
/***/ public String notAGitDirectory;
+ /***/ public String notAMIDX;
/***/ public String notAPACKFile;
/***/ public String notARef;
/***/ public String notASCIIString;
@@ -578,6 +605,7 @@ public class JGitText extends TranslationBundle {
/***/ public String notMergedExceptionMessage;
/***/ public String notShallowedUnshallow;
/***/ public String noXMLParserAvailable;
+ /***/ public String nullRevCommit;
/***/ public String numberDoesntFit;
/***/ public String objectAtHasBadZlibStream;
/***/ public String objectIsCorrupt;
@@ -589,7 +617,6 @@ public class JGitText extends TranslationBundle {
/***/ public String oldIdMustNotBeNull;
/***/ public String onlyOneFetchSupported;
/***/ public String onlyOneOperationCallPerConnectionIsSupported;
- /***/ public String onlyOpenPgpSupportedForSigning;
/***/ public String openFilesMustBeAtLeast1;
/***/ public String openingConnection;
/***/ public String operationCanceled;
@@ -611,6 +638,8 @@ public class JGitText extends TranslationBundle {
/***/ public String packingCancelledDuringObjectsWriting;
/***/ public String packObjectCountMismatch;
/***/ public String packRefs;
+ /***/ public String packRefsFailed;
+ /***/ public String packRefsSuccessful;
/***/ public String packSizeNotSetYet;
/***/ public String packTooLargeForIndexVersion1;
/***/ public String packWasDeleted;
@@ -627,6 +656,7 @@ public class JGitText extends TranslationBundle {
/***/ public String personIdentEmailNonNull;
/***/ public String personIdentNameNonNull;
/***/ public String postCommitHookFailed;
+ /***/ public String precedenceTrustConfig;
/***/ public String prefixRemote;
/***/ public String problemWithResolvingPushRefSpecsLocally;
/***/ public String progressMonUploading;
@@ -654,8 +684,6 @@ public class JGitText extends TranslationBundle {
/***/ public String readerIsRequired;
/***/ public String readingObjectsFromLocalRepositoryFailed;
/***/ public String readLastModifiedFailed;
- /***/ public String readPipeIsNotAllowed;
- /***/ public String readPipeIsNotAllowedRequiredPermission;
/***/ public String readTimedOut;
/***/ public String receivePackObjectTooLarge1;
/***/ public String receivePackObjectTooLarge2;
@@ -732,6 +760,11 @@ public class JGitText extends TranslationBundle {
/***/ public String shortReadOfBlock;
/***/ public String shortReadOfOptionalDIRCExtensionExpectedAnotherBytes;
/***/ public String shortSkipOfBlock;
+ /***/ public String shutdownCleanup;
+ /***/ public String shutdownCleanupFailed;
+ /***/ public String shutdownCleanupListenerFailed;
+ /***/ public String signatureServiceConflict;
+ /***/ public String signatureTypeUnknown;
/***/ public String signatureVerificationError;
/***/ public String signatureVerificationUnavailable;
/***/ public String signedTagMessageNoLf;
@@ -818,6 +851,7 @@ public class JGitText extends TranslationBundle {
/***/ public String unableToCheckConnectivity;
/***/ public String unableToCreateNewObject;
/***/ public String unableToReadFullInt;
+ /***/ public String unableToReadFullArray;
/***/ public String unableToReadPackfile;
/***/ public String unableToRemovePath;
/***/ public String unableToWrite;
@@ -843,6 +877,7 @@ public class JGitText extends TranslationBundle {
/***/ public String unknownObjectInIndex;
/***/ public String unknownObjectType;
/***/ public String unknownObjectType2;
+ /***/ public String unknownPackExtension;
/***/ public String unknownPositionEncoding;
/***/ public String unknownRefStorageFormat;
/***/ public String unknownRepositoryFormat;
@@ -854,6 +889,8 @@ public class JGitText extends TranslationBundle {
/***/ public String unmergedPaths;
/***/ public String unpackException;
/***/ public String unreadableCommitGraph;
+ /***/ public String unreadableMIDX;
+ /***/ public String unreadableObjectSizeIndex;
/***/ public String unreadablePackIndex;
/***/ public String unrecognizedPackExtension;
/***/ public String unrecognizedRef;
@@ -866,6 +903,9 @@ public class JGitText extends TranslationBundle {
/***/ public String unsupportedEncryptionVersion;
/***/ public String unsupportedGC;
/***/ public String unsupportedMark;
+ /***/ public String unsupportedMIDXVersion;
+ /***/ public String unsupportedObjectIdVersion;
+ /***/ public String unsupportedObjectSizeIndexVersion;
/***/ public String unsupportedOperationNotAddAtEnd;
/***/ public String unsupportedPackIndexVersion;
/***/ public String unsupportedPackReverseIndexVersion;
@@ -874,6 +914,7 @@ public class JGitText extends TranslationBundle {
/***/ public String unsupportedRepositoryDescription;
/***/ public String unsupportedSizesObjSizeIndex;
/***/ public String updateRequiresOldIdAndNewId;
+ /***/ public String updatingConfig;
/***/ public String updatingHeadFailed;
/***/ public String updatingReferences;
/***/ public String updatingRefFailed;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diff/FilteredRenameDetector.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diff/FilteredRenameDetector.java
index d65624fc6a..56e5fa46d3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diff/FilteredRenameDetector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diff/FilteredRenameDetector.java
@@ -48,12 +48,15 @@ public class FilteredRenameDetector {
}
/**
+ * Compute diff entries
+ *
* @param diffs
* The set of changes to check.
* @param pathFilter
* Filter out changes that didn't affect this path.
* @return The subset of changes that affect only the filtered path.
* @throws IOException
+ * if an IO error occurred
*/
public List<DiffEntry> compute(List<DiffEntry> diffs,
PathFilter pathFilter) throws IOException {
@@ -73,6 +76,7 @@ public class FilteredRenameDetector {
* Filter out changes that didn't affect these paths.
* @return The subset of changes that affect only the filtered paths.
* @throws IOException
+ * if an IO error occurred
* @see RenameDetector#compute()
*/
public List<DiffEntry> compute(List<DiffEntry> changes,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java
index ebef5247e6..c64a844af1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandExecutor.java
@@ -54,6 +54,8 @@ public class CommandExecutor {
}
/**
+ * Run command
+ *
* @param command
* the command string
* @param workingDir
@@ -62,8 +64,11 @@ public class CommandExecutor {
* the environment
* @return the execution result
* @throws ToolException
+ * if a tool raised an error
* @throws InterruptedException
+ * if thread was interrupted
* @throws IOException
+ * if an IO error occurred
*/
public ExecutionResult run(String command, File workingDir,
Map<String, String> env)
@@ -101,6 +106,8 @@ public class CommandExecutor {
}
/**
+ * Check whether executable file is available
+ *
* @param path
* the executable path
* @param workingDir
@@ -109,8 +116,11 @@ public class CommandExecutor {
* the environment
* @return the execution result
* @throws ToolException
+ * if a tool raised an error
* @throws InterruptedException
+ * if thread was interrupted
* @throws IOException
+ * if an IO error occurred
*/
public boolean checkExecutable(String path, File workingDir,
Map<String, String> env)
@@ -155,6 +165,9 @@ public class CommandExecutor {
if (fs instanceof FS_POSIX) {
commandArray = new String[1];
commandArray[0] = commandFile.getCanonicalPath();
+ } else if (fs instanceof FS_Win32_Cygwin) {
+ commandArray = new String[1];
+ commandArray[0] = commandFile.getCanonicalPath().replace("\\", "/"); //$NON-NLS-1$ //$NON-NLS-2$
} else if (fs instanceof FS_Win32) {
if (useMsys2) {
commandArray = new String[3];
@@ -166,9 +179,6 @@ public class CommandExecutor {
commandArray = new String[1];
commandArray[0] = commandFile.getCanonicalPath();
}
- } else if (fs instanceof FS_Win32_Cygwin) {
- commandArray = new String[1];
- commandArray[0] = commandFile.getCanonicalPath().replace("\\", "/"); //$NON-NLS-1$ //$NON-NLS-2$
} else {
throw new ToolException(
"JGit: file system not supported: " + fs.toString()); //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java
index 00dec32718..1e09796d23 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineDiffTool.java
@@ -205,6 +205,8 @@ public enum CommandLineDiffTool {
private final String parameters;
/**
+ * Get path
+ *
* @return path
*/
public String getPath() {
@@ -212,6 +214,8 @@ public enum CommandLineDiffTool {
}
/**
+ * Get parameters
+ *
* @return parameters as one string
*/
public String getParameters() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineMergeTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineMergeTool.java
index 3a22124328..d8287f45d1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineMergeTool.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/CommandLineMergeTool.java
@@ -273,11 +273,6 @@ public enum CommandLineMergeTool {
this.exitCodeTrustable = exitCodeTrustable;
}
- CommandLineMergeTool(CommandLineMergeTool from) {
- this(from.getPath(), from.getParameters(true),
- from.getParameters(false), from.isExitCodeTrustable());
- }
-
CommandLineMergeTool(String path, CommandLineMergeTool from) {
this(path, from.getParameters(true), from.getParameters(false),
from.isExitCodeTrustable());
@@ -292,6 +287,8 @@ public enum CommandLineMergeTool {
private final boolean exitCodeTrustable;
/**
+ * Get path
+ *
* @return path
*/
public String getPath() {
@@ -299,6 +296,8 @@ public enum CommandLineMergeTool {
}
/**
+ * Get parameters
+ *
* @param withBase
* return parameters with base present?
* @return parameters with or without base present
@@ -311,6 +310,8 @@ public enum CommandLineMergeTool {
}
/**
+ * Whether exit code can be trusted
+ *
* @return parameters
*/
public boolean isExitCodeTrustable() {
@@ -318,6 +319,8 @@ public enum CommandLineMergeTool {
}
/**
+ * Whether command with with base present is valid
+ *
* @return true if command with base present is valid, false otherwise
*/
public boolean canMergeWithoutBasePresent() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java
index c8b04f90f2..e74337a8a1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffToolConfig.java
@@ -75,6 +75,8 @@ public class DiffToolConfig {
}
/**
+ * Get default tool name
+ *
* @return the default diff tool name (diff.tool)
*/
public String getDefaultToolName() {
@@ -82,6 +84,8 @@ public class DiffToolConfig {
}
/**
+ * Get default GUI tool name
+ *
* @return the default GUI diff tool name (diff.guitool)
*/
public String getDefaultGuiToolName() {
@@ -89,6 +93,8 @@ public class DiffToolConfig {
}
/**
+ * Get difftool.prompt option
+ *
* @return the diff tool "prompt" option (difftool.prompt)
*/
public boolean isPrompt() {
@@ -96,6 +102,8 @@ public class DiffToolConfig {
}
/**
+ * Get difftool.trustExitCode option
+ *
* @return the diff tool "trust exit code" option (difftool.trustExitCode)
*/
public boolean isTrustExitCode() {
@@ -103,6 +111,8 @@ public class DiffToolConfig {
}
/**
+ * Get tools map
+ *
* @return the tools map
*/
public Map<String, ExternalDiffTool> getTools() {
@@ -110,6 +120,8 @@ public class DiffToolConfig {
}
/**
+ * Get tool names
+ *
* @return the tool names
*/
public Set<String> getToolNames() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
index d0034df3bc..6a67bf3a1a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/DiffTools.java
@@ -172,6 +172,7 @@ public class DiffTools {
* the "trust exit code" option
* @return the execution result from tool
* @throws ToolException
+ * when the tool fails
*/
public ExecutionResult compare(FileElement localFile,
FileElement remoteFile, ExternalDiffTool tool,
@@ -244,6 +245,7 @@ public class DiffTools {
* path to the node in repository to parse git attributes for
* @return name of the difftool if set
* @throws ToolException
+ * when the tool failed
*/
public Optional<String> getExternalToolFromAttributes(final String path)
throws ToolException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalDiffTool.java
index e01b892a53..e02697b772 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalDiffTool.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalDiffTool.java
@@ -16,21 +16,29 @@ package org.eclipse.jgit.internal.diffmergetool;
public interface ExternalDiffTool {
/**
+ * Get tool name
+ *
* @return the tool name
*/
String getName();
/**
+ * Get tool path
+ *
* @return the tool path
*/
String getPath();
/**
+ * Get tool command
+ *
* @return the tool command
*/
String getCommand();
/**
+ * Whether tool is available
+ *
* @return availability of the tool: true if tool can be executed and false
* if not
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalMergeTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalMergeTool.java
index 0c3ddf9afe..022cd27f44 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalMergeTool.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalMergeTool.java
@@ -18,11 +18,15 @@ import org.eclipse.jgit.lib.internal.BooleanTriState;
public interface ExternalMergeTool extends ExternalDiffTool {
/**
+ * Get the tool "trust exit code" option
+ *
* @return the tool "trust exit code" option
*/
BooleanTriState getTrustExitCode();
/**
+ * Get tool command
+ *
* @param withBase
* get command with base present (true) or without base present
* (false)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalToolUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalToolUtils.java
index b2dd846d70..e5947102eb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalToolUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ExternalToolUtils.java
@@ -57,6 +57,7 @@ public class ExternalToolUtils {
* the base file (can be null)
* @return the prepared (with replaced variables) command string
* @throws IOException
+ * if an IO error occurred
*/
public static String prepareCommand(String command, FileElement localFile,
FileElement remoteFile, FileElement mergedFile,
@@ -91,6 +92,7 @@ public class ExternalToolUtils {
* the base file (can be null)
* @return the environment map with variables and values (file paths)
* @throws IOException
+ * if an IO error occurred
*/
public static Map<String, String> prepareEnvironment(File gitDir,
FileElement localFile, FileElement remoteFile,
@@ -115,6 +117,8 @@ public class ExternalToolUtils {
}
/**
+ * Quote path
+ *
* @param path
* the path to be quoted
* @return quoted path if it contains spaces
@@ -136,6 +140,8 @@ public class ExternalToolUtils {
}
/**
+ * Whether tool is available
+ *
* @param fs
* the file system abstraction
* @param gitDir
@@ -160,6 +166,8 @@ public class ExternalToolUtils {
}
/**
+ * Create sorted tool set
+ *
* @param defaultName
* the default tool name
* @param userDefinedNames
@@ -209,6 +217,7 @@ public class ExternalToolUtils {
* config key name for the tool
* @return attribute value for the given tool key if set
* @throws ToolException
+ * if the tool failed
*/
public static Optional<String> getExternalToolFromAttributes(
final Repository repository, final String path,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/FileElement.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/FileElement.java
index ba8ca54c58..37eb2d9f7d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/FileElement.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/FileElement.java
@@ -28,7 +28,6 @@ public class FileElement {
/**
* The file element type.
- *
*/
public enum Type {
/**
@@ -91,6 +90,8 @@ public class FileElement {
}
/**
+ * Create file element
+ *
* @param path
* the file path
* @param type
@@ -111,6 +112,8 @@ public class FileElement {
}
/**
+ * Get path
+ *
* @return the file path
*/
public String getPath() {
@@ -118,6 +121,8 @@ public class FileElement {
}
/**
+ * Get type
+ *
* @return the element type
*/
public Type getType() {
@@ -125,6 +130,8 @@ public class FileElement {
}
/**
+ * Get file
+ * <p>
* Return
* <ul>
* <li>a temporary file if already created and stream is not valid</li>
@@ -138,6 +145,7 @@ public class FileElement {
*
* @return the object stream
* @throws IOException
+ * if an IO error occurred
*/
public File getFile() throws IOException {
// if we have already temp file and no stream
@@ -179,6 +187,7 @@ public class FileElement {
* temporary directory is used
* @return temporary file in directory or in the system temporary directory
* @throws IOException
+ * if an IO error occurred
*/
public File createTempFile(File directory) throws IOException {
if (tempFile == null) {
@@ -204,6 +213,7 @@ public class FileElement {
* the input string
* @return the replaced input string
* @throws IOException
+ * if an IO error occurred
*/
public String replaceVariable(String input) throws IOException {
return input.replace("$" + type.name(), getFile().getPath()); //$NON-NLS-1$
@@ -215,6 +225,7 @@ public class FileElement {
* @param env
* the environment where this element should be added
* @throws IOException
+ * if an IO error occurred
*/
public void addToEnv(Map<String, String> env) throws IOException {
env.put(type.name(), getFile().getPath());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeToolConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeToolConfig.java
index 9625d5f101..40cb820602 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeToolConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeToolConfig.java
@@ -89,6 +89,8 @@ public class MergeToolConfig {
}
/**
+ * Get default tool name
+ *
* @return the default merge tool name (merge.tool)
*/
public String getDefaultToolName() {
@@ -96,6 +98,8 @@ public class MergeToolConfig {
}
/**
+ * Get default GUI tool name
+ *
* @return the default GUI merge tool name (merge.guitool)
*/
public String getDefaultGuiToolName() {
@@ -103,6 +107,8 @@ public class MergeToolConfig {
}
/**
+ * Get mergetool.prompt option
+ *
* @return the merge tool "prompt" option (mergetool.prompt)
*/
public boolean isPrompt() {
@@ -110,6 +116,8 @@ public class MergeToolConfig {
}
/**
+ * Get tool "keep backup" option
+ *
* @return the tool "keep backup" option
*/
public boolean isKeepBackup() {
@@ -117,6 +125,8 @@ public class MergeToolConfig {
}
/**
+ * Get tool "keep temporaries" option
+ *
* @return the tool "keepTemporaries" option
*/
public boolean isKeepTemporaries() {
@@ -124,6 +134,8 @@ public class MergeToolConfig {
}
/**
+ * Get the tool "write to temp" option
+ *
* @return the tool "write to temp" option
*/
public boolean isWriteToTemp() {
@@ -131,6 +143,8 @@ public class MergeToolConfig {
}
/**
+ * Get the tools map
+ *
* @return the tools map
*/
public Map<String, ExternalMergeTool> getTools() {
@@ -138,6 +152,8 @@ public class MergeToolConfig {
}
/**
+ * Get tool names
+ *
* @return the tool names
*/
public Set<String> getToolNames() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java
index b903201264..213ce6871e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/MergeTools.java
@@ -26,14 +26,13 @@ import java.util.Set;
import java.util.TreeMap;
import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.internal.diffmergetool.FileElement.Type;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.lib.internal.BooleanTriState;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.FS;
-import org.eclipse.jgit.util.StringUtils;
import org.eclipse.jgit.util.FS.ExecutionResult;
+import org.eclipse.jgit.util.StringUtils;
/**
* Manages merge tools.
@@ -184,6 +183,7 @@ public class MergeTools {
* the selected tool
* @return the execution result from tool
* @throws ToolException
+ * if the tool failed
*/
public ExecutionResult merge(FileElement localFile, FileElement remoteFile,
FileElement mergedFile, FileElement baseFile, File tempDir,
@@ -241,7 +241,7 @@ public class MergeTools {
FileElement backup = null;
Path path = Paths.get(from.getPath());
if (Files.exists(path)) {
- backup = new FileElement(from.getPath(), Type.BACKUP);
+ backup = new FileElement(from.getPath(), FileElement.Type.BACKUP);
Files.copy(path, backup.createTempFile(toParentDir).toPath(),
StandardCopyOption.REPLACE_EXISTING);
}
@@ -254,6 +254,7 @@ public class MergeTools {
* @return the created temporary directory if (mergetol.writeToTemp == true)
* or null if not configured or false.
* @throws IOException
+ * if an IO error occurred
*/
public File createTempDirectory() throws IOException {
return config.isWriteToTemp()
@@ -271,6 +272,8 @@ public class MergeTools {
}
/**
+ * Get predefined tool names
+ *
* @return the predefined tool names
*/
public Set<String> getPredefinedToolNames() {
@@ -305,6 +308,7 @@ public class MergeTools {
* path to the node in repository to parse git attributes for
* @return name of the difftool if set
* @throws ToolException
+ * if the tool failed
*/
public Optional<String> getExternalToolFromAttributes(final String path)
throws ToolException {
@@ -329,6 +333,8 @@ public class MergeTools {
}
/**
+ * Get user defined tools
+ *
* @return the user defined tools
*/
public Map<String, ExternalMergeTool> getUserDefinedTools() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java
index e1169a2d60..c1d69b4f11 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/PreDefinedDiffTool.java
@@ -43,6 +43,7 @@ public class PreDefinedDiffTool extends UserDefinedDiffTool {
/**
* @param path
+ * path string
*/
@Override
public void setPath(String path) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java
index 73d3588906..cd11325433 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/ToolException.java
@@ -34,13 +34,15 @@ public class ToolException extends Exception {
private static final long serialVersionUID = 1L;
/**
- *
+ * Create tool exception
*/
public ToolException() {
this(null, null, false);
}
/**
+ * Create tool exception
+ *
* @param message
* the exception message
*/
@@ -49,6 +51,8 @@ public class ToolException extends Exception {
}
/**
+ * Create tool exception
+ *
* @param message
* the exception message
* @param result
@@ -64,6 +68,8 @@ public class ToolException extends Exception {
}
/**
+ * Create tool exception
+ *
* @param message
* the exception message
* @param cause
@@ -76,6 +82,8 @@ public class ToolException extends Exception {
}
/**
+ * Create tool exception
+ *
* @param cause
* the cause for throw
*/
@@ -86,6 +94,8 @@ public class ToolException extends Exception {
}
/**
+ * Whether result is valid
+ *
* @return true if result is valid, false else
*/
public boolean isResult() {
@@ -93,6 +103,8 @@ public class ToolException extends Exception {
}
/**
+ * Get execution result
+ *
* @return the execution result
*/
public ExecutionResult getResult() {
@@ -100,6 +112,8 @@ public class ToolException extends Exception {
}
/**
+ * Whether execution failed with an error
+ *
* @return true if command execution error appears, false otherwise
*/
public boolean isCommandExecutionError() {
@@ -107,6 +121,8 @@ public class ToolException extends Exception {
}
/**
+ * Get buffered stderr as a String
+ *
* @return the result Stderr
*/
public String getResultStderr() {
@@ -123,6 +139,8 @@ public class ToolException extends Exception {
}
/**
+ * Get buffered stdout as a String
+ *
* @return the result Stdout
*/
public String getResultStdout() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java
index eb72d01cdb..62bde28feb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedDiffTool.java
@@ -110,6 +110,8 @@ public class UserDefinedDiffTool implements ExternalDiffTool {
}
/**
+ * Set whether tool is available
+ *
* @param available
* true if tool can be found and false if not
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedMergeTool.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedMergeTool.java
index 1dd2f0d793..b1a5a22ef5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedMergeTool.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/diffmergetool/UserDefinedMergeTool.java
@@ -49,6 +49,8 @@ public class UserDefinedMergeTool extends UserDefinedDiffTool
}
/**
+ * Set "trust exit code" flag
+ *
* @param trustExitCode
* the new "trust exit code" flag
*/
@@ -57,9 +59,11 @@ public class UserDefinedMergeTool extends UserDefinedDiffTool
}
/**
+ * Get command
+ *
* @param withBase
* not used, because user-defined merge tool can only define one
- * cmd -> it must handle with and without base present (empty)
+ * cmd -&gt; it must handle with and without base present (empty)
* @return the tool command
*/
@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java
index 8c1f773e19..e173ab5edc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckError.java
@@ -46,17 +46,29 @@ public class FsckError {
this.errorType = errorType;
}
- /** @return identifier of the object. */
+ /**
+ * Get Id
+ *
+ * @return identifier of the object.
+ */
public ObjectId getId() {
return id;
}
- /** @return type of the object. */
+ /**
+ * Get type
+ *
+ * @return type of the object.
+ */
public int getType() {
return type;
}
- /** @return error type of the corruption. */
+ /**
+ * Get error type
+ *
+ * @return error type of the corruption.
+ */
@Nullable
public ObjectChecker.ErrorType getErrorType() {
return errorType;
@@ -81,12 +93,20 @@ public class FsckError {
this.errorType = errorType;
}
- /** @return the file name of the index file. */
+ /**
+ * Get file name
+ *
+ * @return the file name of the index file.
+ */
public String getFileName() {
return fileName;
}
- /** @return the error type of the corruption. */
+ /**
+ * Get error type
+ *
+ * @return the error type of the corruption.
+ */
public ErrorType getErrorType() {
return errorType;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java
index 3b94984561..795f4404e8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/fsck/FsckPackParser.java
@@ -66,7 +66,6 @@ public class FsckPackParser extends PackParser {
this.blockSize = channel.blockSize() > 0 ? channel.blockSize() : 65536;
}
- /** {@inheritDoc} */
@Override
protected void onPackHeader(long objCnt) throws IOException {
if (expectedObjectCount >= 0) {
@@ -78,48 +77,41 @@ public class FsckPackParser extends PackParser {
}
}
- /** {@inheritDoc} */
@Override
protected void onBeginWholeObject(long streamPosition, int type,
long inflatedSize) throws IOException {
crc.reset();
}
- /** {@inheritDoc} */
@Override
protected void onObjectHeader(Source src, byte[] raw, int pos, int len)
throws IOException {
crc.update(raw, pos, len);
}
- /** {@inheritDoc} */
@Override
protected void onObjectData(Source src, byte[] raw, int pos, int len)
throws IOException {
crc.update(raw, pos, len);
}
- /** {@inheritDoc} */
@Override
protected void onEndWholeObject(PackedObjectInfo info) throws IOException {
info.setCRC((int) crc.getValue());
}
- /** {@inheritDoc} */
@Override
protected void onBeginOfsDelta(long deltaStreamPosition,
long baseStreamPosition, long inflatedSize) throws IOException {
crc.reset();
}
- /** {@inheritDoc} */
@Override
protected void onBeginRefDelta(long deltaStreamPosition, AnyObjectId baseId,
long inflatedSize) throws IOException {
crc.reset();
}
- /** {@inheritDoc} */
@Override
protected UnresolvedDelta onEndDelta() throws IOException {
UnresolvedDelta delta = new UnresolvedDelta();
@@ -127,14 +119,12 @@ public class FsckPackParser extends PackParser {
return delta;
}
- /** {@inheritDoc} */
@Override
protected void onInflatedObjectData(PackedObjectInfo obj, int typeCode,
byte[] data) throws IOException {
// FsckPackParser ignores this event.
}
- /** {@inheritDoc} */
@Override
protected void verifySafeObject(final AnyObjectId id, final int type,
final byte[] data) {
@@ -146,13 +136,11 @@ public class FsckPackParser extends PackParser {
}
}
- /** {@inheritDoc} */
@Override
protected void onPackFooter(byte[] hash) throws IOException {
// Do nothing.
}
- /** {@inheritDoc} */
@Override
protected boolean onAppendBase(int typeCode, byte[] data,
PackedObjectInfo info) throws IOException {
@@ -160,13 +148,11 @@ public class FsckPackParser extends PackParser {
return false;
}
- /** {@inheritDoc} */
@Override
protected void onEndThinPack() throws IOException {
// Do nothing.
}
- /** {@inheritDoc} */
@Override
protected ObjectTypeAndSize seekDatabase(PackedObjectInfo obj,
ObjectTypeAndSize info) throws IOException {
@@ -175,7 +161,6 @@ public class FsckPackParser extends PackParser {
return readObjectHeader(info);
}
- /** {@inheritDoc} */
@Override
protected ObjectTypeAndSize seekDatabase(UnresolvedDelta delta,
ObjectTypeAndSize info) throws IOException {
@@ -184,7 +169,6 @@ public class FsckPackParser extends PackParser {
return readObjectHeader(info);
}
- /** {@inheritDoc} */
@Override
protected int readDatabase(byte[] dst, int pos, int cnt)
throws IOException {
@@ -229,13 +213,11 @@ public class FsckPackParser extends PackParser {
return buf.array();
}
- /** {@inheritDoc} */
@Override
protected boolean checkCRC(int oldCRC) {
return oldCRC == (int) crc.getValue();
}
- /** {@inheritDoc} */
@Override
protected void onStoreStream(byte[] raw, int pos, int len)
throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddToBitmapFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddToBitmapFilter.java
index 86d4722000..84f1cb968a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddToBitmapFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddToBitmapFilter.java
@@ -40,7 +40,6 @@ public class AddToBitmapFilter extends RevFilter {
this.bitmap = bitmap;
}
- /** {@inheritDoc} */
@Override
public final boolean include(RevWalk walker, RevCommit cmit) {
Bitmap visitedBitmap;
@@ -61,13 +60,11 @@ public class AddToBitmapFilter extends RevFilter {
return false;
}
- /** {@inheritDoc} */
@Override
public final RevFilter clone() {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public final boolean requiresCommitBody() {
return false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddToBitmapWithCacheFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddToBitmapWithCacheFilter.java
index d7ccadfbe7..a95432ce73 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddToBitmapWithCacheFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddToBitmapWithCacheFilter.java
@@ -53,7 +53,6 @@ public class AddToBitmapWithCacheFilter extends RevFilter {
this.bitmap = bitmap;
}
- /** {@inheritDoc} */
@Override
public final boolean include(RevWalk rw, RevCommit c) {
Bitmap visitedBitmap;
@@ -76,13 +75,11 @@ public class AddToBitmapWithCacheFilter extends RevFilter {
return false;
}
- /** {@inheritDoc} */
@Override
public final RevFilter clone() {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public final boolean requiresCommitBody() {
return false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddUnseenToBitmapFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddUnseenToBitmapFilter.java
index da9f9d031e..5ac267e789 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddUnseenToBitmapFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/AddUnseenToBitmapFilter.java
@@ -48,7 +48,6 @@ public class AddUnseenToBitmapFilter extends RevFilter {
this.bitmap = bitmap;
}
- /** {@inheritDoc} */
@Override
public final boolean include(RevWalk walker, RevCommit cmit) {
Bitmap visitedBitmap;
@@ -69,13 +68,11 @@ public class AddUnseenToBitmapFilter extends RevFilter {
return false;
}
- /** {@inheritDoc} */
@Override
public final RevFilter clone() {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public final boolean requiresCommitBody() {
return false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/BitmappedReachabilityChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/BitmappedReachabilityChecker.java
index 37721ad1ea..f5f51e4830 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/BitmappedReachabilityChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/revwalk/BitmappedReachabilityChecker.java
@@ -119,7 +119,6 @@ public class BitmappedReachabilityChecker implements ReachabilityChecker {
this.reached = repoBitmaps.newBitmapBuilder();
}
- /** {@inheritDoc} */
@Override
public final boolean include(RevWalk walker, RevCommit cmit) {
Bitmap commitBitmap;
@@ -149,13 +148,11 @@ public class BitmappedReachabilityChecker implements ReachabilityChecker {
}
}
- /** {@inheritDoc} */
@Override
public final RevFilter clone() {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public final boolean requiresCommitBody() {
return false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/ChangedPathFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/ChangedPathFilter.java
new file mode 100644
index 0000000000..ad3ce50181
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/ChangedPathFilter.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2023, Google LLC
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.commitgraph;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.util.Set;
+
+import org.apache.commons.codec.digest.MurmurHash3;
+
+/**
+ * A changed path filter for a commit.
+ *
+ * @since 6.7
+ */
+public class ChangedPathFilter {
+ /**
+ * The number of times a path is hashed, as described in man
+ * gitformat-commit-graph(5). The value of this constant is the only value
+ * JGit currently supports.
+ */
+ public static final int PATH_HASH_COUNT = 7;
+
+ /**
+ * The minimum bits per entry, as described in man
+ * gitformat-commit-graph(5). The value of this constant is the only value
+ * JGit currently supports.
+ */
+ public static final int BITS_PER_ENTRY = 10;
+
+ /**
+ * Seed value as described in man gitformat-commit-graph(5).
+ */
+ private static final int SEED1 = 0x293ae76f;
+
+ /**
+ * Seed value as described in man gitformat-commit-graph(5).
+ */
+ private static final int SEED2 = 0x7e646e2c;
+
+ /**
+ * A filter that matches every path.
+ */
+ public static final ChangedPathFilter FULL = new ChangedPathFilter(
+ new byte[] { (byte) 0xff }, 0, 1);
+
+ private static final ChangedPathFilter EMPTY = new ChangedPathFilter(
+ new byte[] { (byte) 0 }, 0, 1);
+
+ private final byte[] data;
+
+ private final int offset;
+
+ private final int length;
+
+ /**
+ * Constructs a changed path filter.
+ *
+ * @param data
+ * data (possibly read from a commit graph file)
+ * @param offset
+ * offset into data
+ * @param length
+ * length of data
+ */
+ private ChangedPathFilter(byte[] data, int offset, int length) {
+ this.data = data;
+ this.offset = offset;
+ this.length = length;
+ }
+
+ /**
+ * Returns a filter that matches all given paths.
+ * <p>
+ * Because of the nature of Bloom filters, this filter may also match paths
+ * not in the given set.
+ *
+ * @param paths
+ * the paths that the filter must match
+ * @return the corresponding filter
+ */
+ @SuppressWarnings("ByteBufferBackingArray")
+ public static ChangedPathFilter fromPaths(Set<ByteBuffer> paths) {
+ if (paths.isEmpty()) {
+ return EMPTY;
+ }
+ byte[] bloom = new byte[-Math
+ .floorDiv(-paths.size() * ChangedPathFilter.BITS_PER_ENTRY, 8)];
+ for (ByteBuffer path : paths) {
+ add(bloom, path.array(), path.position(),
+ path.limit() - path.position());
+ }
+ return new ChangedPathFilter(bloom, 0, bloom.length);
+ }
+
+ /**
+ * Returns a filter read from a file.
+ *
+ * @param data
+ * data (read from a commit graph file)
+ * @param offset
+ * offset into data
+ * @param length
+ * length of data
+ *
+ * @return the corresponding filter
+ */
+ public static ChangedPathFilter fromFile(byte[] data, int offset,
+ int length) {
+ return new ChangedPathFilter(data, offset, length);
+ }
+
+ private static void add(byte[] changedPathFilterData, byte[] path,
+ int offset, int length) {
+
+ int hash0 = MurmurHash3.hash32x86(path, offset, length, SEED1);
+ int hash1 = MurmurHash3.hash32x86(path, offset, length, SEED2);
+ for (int i = 0; i < PATH_HASH_COUNT; i++) {
+ int pos = Integer.remainderUnsigned(hash0 + i * hash1,
+ changedPathFilterData.length * 8);
+ changedPathFilterData[pos / 8] |= (byte) (1 << (pos % 8));
+ }
+ }
+
+ /**
+ * Checks if this changed path filter could contain path.
+ *
+ * @param path
+ * path to check existence of
+ * @return true if the filter could contain path, false if the filter
+ * definitely does not contain path
+ */
+ public boolean maybeContains(byte[] path) {
+ int hash0 = MurmurHash3.hash32x86(path, 0, path.length, SEED1);
+ int hash1 = MurmurHash3.hash32x86(path, 0, path.length, SEED2);
+ int bloomFilterBits = length * 8;
+ for (int i = 0; i < PATH_HASH_COUNT; i++) {
+ int pos = Integer.remainderUnsigned(hash0 + i * hash1,
+ bloomFilterBits);
+ if ((data[offset + (pos / 8)] & (byte) (1 << (pos % 8))) == 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Writes this filter to the given stream.
+ *
+ * @param s
+ * stream to write to
+ */
+ public void writeTo(ByteArrayOutputStream s) {
+ s.write(data, offset, length);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraph.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraph.java
index 0796293f52..d1178c2850 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraph.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraph.java
@@ -32,25 +32,26 @@ public interface CommitGraph {
/** Empty {@link CommitGraph} with no results. */
CommitGraph EMPTY = new CommitGraph() {
- /** {@inheritDoc} */
@Override
public int findGraphPosition(AnyObjectId commit) {
return -1;
}
- /** {@inheritDoc} */
@Override
public CommitData getCommitData(int graphPos) {
return null;
}
- /** {@inheritDoc} */
@Override
public ObjectId getObjectId(int graphPos) {
return null;
}
- /** {@inheritDoc} */
+ @Override
+ public ChangedPathFilter getChangedPathFilter(int graphPos) {
+ return null;
+ }
+
@Override
public long getCommitCnt() {
return 0;
@@ -97,6 +98,15 @@ public interface CommitGraph {
ObjectId getObjectId(int graphPos);
/**
+ * Get the changed path filter of the object at the commit-graph position.
+ *
+ * @param graphPos
+ * the position in the commit-graph of the object.
+ * @return the bloom filter or null if it's not found.
+ */
+ ChangedPathFilter getChangedPathFilter(int graphPos);
+
+ /**
* Obtain the total number of commits described by this commit-graph.
*
* @return number of commits in this commit-graph.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphBuilder.java
index a6af3bc592..5e993eab22 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphBuilder.java
@@ -10,6 +10,8 @@
package org.eclipse.jgit.internal.storage.commitgraph;
+import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_BLOOM_FILTER_DATA;
+import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_BLOOM_FILTER_INDEX;
import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_COMMIT_DATA;
import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_EXTRA_EDGE_LIST;
import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_OID_FANOUT;
@@ -35,7 +37,15 @@ class CommitGraphBuilder {
private byte[] extraList;
- /** @return A builder of {@link CommitGraph}. */
+ private byte[] bloomFilterIndex;
+
+ private byte[] bloomFilterData;
+
+ /**
+ * Create builder
+ *
+ * @return A builder of {@link CommitGraph}.
+ */
static CommitGraphBuilder builder() {
return new CommitGraphBuilder(OBJECT_ID_LENGTH);
}
@@ -72,6 +82,20 @@ class CommitGraphBuilder {
return this;
}
+ CommitGraphBuilder addBloomFilterIndex(byte[] buffer)
+ throws CommitGraphFormatException {
+ assertChunkNotSeenYet(bloomFilterIndex, CHUNK_ID_BLOOM_FILTER_INDEX);
+ bloomFilterIndex = buffer;
+ return this;
+ }
+
+ CommitGraphBuilder addBloomFilterData(byte[] buffer)
+ throws CommitGraphFormatException {
+ assertChunkNotSeenYet(bloomFilterData, CHUNK_ID_BLOOM_FILTER_DATA);
+ bloomFilterData = buffer;
+ return this;
+ }
+
CommitGraph build() throws CommitGraphFormatException {
assertChunkNotNull(oidFanout, CHUNK_ID_OID_FANOUT);
assertChunkNotNull(oidLookup, CHUNK_ID_OID_LOOKUP);
@@ -81,7 +105,9 @@ class CommitGraphBuilder {
oidLookup);
GraphCommitData commitDataChunk = new GraphCommitData(hashLength,
commitData, extraList);
- return new CommitGraphV1(index, commitDataChunk);
+ GraphChangedPathFilterData cpfData = new GraphChangedPathFilterData(
+ bloomFilterIndex, bloomFilterData);
+ return new CommitGraphV1(index, commitDataChunk, cpfData);
}
private void assertChunkNotNull(Object object, int chunkId)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphConstants.java
index a074833fa5..8d2789cf52 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphConstants.java
@@ -25,6 +25,10 @@ class CommitGraphConstants {
static final int CHUNK_ID_EXTRA_EDGE_LIST = 0x45444745; /* "EDGE" */
+ static final int CHUNK_ID_BLOOM_FILTER_INDEX = 0x42494458; /* "BIDX" */
+
+ static final int CHUNK_ID_BLOOM_FILTER_DATA = 0x42444154; /* "BDAT" */
+
/**
* First 4 bytes describe the chunk id. Value 0 is a terminating label.
* Other 8 bytes provide the byte-offset in current file for chunk to start.
@@ -41,15 +45,15 @@ class CommitGraphConstants {
/** Mask to make the last edgeValue into position */
static final int GRAPH_EDGE_LAST_MASK = 0x7fffffff;
- /** EdgeValue & GRAPH_LAST_EDGE != 0 means it is the last edgeValue */
+ /** EdgeValue &amp; GRAPH_LAST_EDGE != 0 means it is the last edgeValue */
static final int GRAPH_LAST_EDGE = 0x80000000;
/** EdgeValue == GRAPH_NO_PARENT means it has no parents */
static final int GRAPH_NO_PARENT = 0x70000000;
/**
- * EdgeValue & GRAPH_EXTRA_EDGES_NEEDED != 0 means its other parents are in
- * Chunk Extra Edge List
+ * EdgeValue &amp; GRAPH_EXTRA_EDGES_NEEDED != 0 means its other parents are
+ * in Chunk Extra Edge List
*/
static final int GRAPH_EXTRA_EDGES_NEEDED = 0x80000000;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphLoader.java
index 571f5f4ebe..7e9220dc0d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphLoader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphLoader.java
@@ -10,6 +10,8 @@
package org.eclipse.jgit.internal.storage.commitgraph;
+import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_BLOOM_FILTER_DATA;
+import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_BLOOM_FILTER_INDEX;
import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_COMMIT_DATA;
import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_EXTRA_EDGE_LIST;
import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_OID_FANOUT;
@@ -25,9 +27,12 @@ import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
+import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.SystemReader;
import org.eclipse.jgit.util.io.SilentFileInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -93,6 +98,46 @@ public class CommitGraphLoader {
*/
public static CommitGraph read(InputStream fd)
throws CommitGraphFormatException, IOException {
+
+ boolean readChangedPathFilters;
+ try {
+ readChangedPathFilters = SystemReader.getInstance().getJGitConfig()
+ .getBoolean(ConfigConstants.CONFIG_COMMIT_GRAPH_SECTION,
+ ConfigConstants.CONFIG_KEY_READ_CHANGED_PATHS,
+ false);
+ } catch (ConfigInvalidException e) {
+ // Use the default value if, for some reason, the config couldn't be
+ // read.
+ readChangedPathFilters = false;
+ }
+
+ return read(fd, readChangedPathFilters);
+ }
+
+ /**
+ * Read an existing commit-graph file from a buffered stream.
+ * <p>
+ * The format of the file will be automatically detected and a proper access
+ * implementation for that format will be constructed and returned to the
+ * caller. The file may or may not be held open by the returned instance.
+ *
+ * @param fd
+ * stream to read the commit-graph file from. The stream must be
+ * buffered as some small IOs are performed against the stream.
+ * The caller is responsible for closing the stream.
+ *
+ * @param readChangedPathFilters
+ * enable reading bloom filter chunks.
+ *
+ * @return a copy of the commit-graph file in memory
+ * @throws CommitGraphFormatException
+ * the commit-graph file's format is different from we expected.
+ * @throws java.io.IOException
+ * the stream cannot be read.
+ */
+ public static CommitGraph read(InputStream fd,
+ boolean readChangedPathFilters)
+ throws CommitGraphFormatException, IOException {
byte[] hdr = new byte[8];
IO.readFully(fd, hdr, 0, hdr.length);
@@ -164,6 +209,16 @@ public class CommitGraphLoader {
case CHUNK_ID_EXTRA_EDGE_LIST:
builder.addExtraList(buffer);
break;
+ case CHUNK_ID_BLOOM_FILTER_INDEX:
+ if (readChangedPathFilters) {
+ builder.addBloomFilterIndex(buffer);
+ }
+ break;
+ case CHUNK_ID_BLOOM_FILTER_DATA:
+ if (readChangedPathFilters) {
+ builder.addBloomFilterData(buffer);
+ }
+ break;
default:
LOG.warn(MessageFormat.format(
JGitText.get().commitGraphChunkUnknown,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphV1.java
index da172192e4..b0a9c83848 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphV1.java
@@ -24,18 +24,20 @@ class CommitGraphV1 implements CommitGraph {
private final GraphCommitData commitData;
- CommitGraphV1(GraphObjectIndex index, GraphCommitData commitData) {
+ private final GraphChangedPathFilterData cpfData;
+
+ CommitGraphV1(GraphObjectIndex index, GraphCommitData commitData,
+ GraphChangedPathFilterData cpfData) {
this.idx = index;
this.commitData = commitData;
+ this.cpfData = cpfData;
}
- /** {@inheritDoc} */
@Override
public int findGraphPosition(AnyObjectId commit) {
return idx.findGraphPosition(commit);
}
- /** {@inheritDoc} */
@Override
public CommitData getCommitData(int graphPos) {
if (graphPos < 0 || graphPos >= getCommitCnt()) {
@@ -44,13 +46,16 @@ class CommitGraphV1 implements CommitGraph {
return commitData.getCommitData(graphPos);
}
- /** {@inheritDoc} */
@Override
public ObjectId getObjectId(int graphPos) {
return idx.getObjectId(graphPos);
}
- /** {@inheritDoc} */
+ @Override
+ public ChangedPathFilter getChangedPathFilter(int graphPos) {
+ return cpfData.getChangedPathFilter(graphPos);
+ }
+
@Override
public long getCommitCnt() {
return idx.getCommitCnt();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphWriter.java
index a58a9eb632..55539e2a66 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/CommitGraphWriter.java
@@ -10,6 +10,8 @@
package org.eclipse.jgit.internal.storage.commitgraph;
+import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_BLOOM_FILTER_DATA;
+import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_BLOOM_FILTER_INDEX;
import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_COMMIT_DATA;
import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_EXTRA_EDGE_LIST;
import static org.eclipse.jgit.internal.storage.commitgraph.CommitGraphConstants.CHUNK_ID_OID_FANOUT;
@@ -24,21 +26,33 @@ import static org.eclipse.jgit.lib.Constants.COMMIT_GENERATION_NOT_COMPUTED;
import static org.eclipse.jgit.lib.Constants.COMMIT_GENERATION_UNKNOWN;
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.OutputStream;
-import java.text.MessageFormat;
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
-import java.util.Stack;
+import java.util.Optional;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.io.CancellableDigestOutputStream;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.EmptyTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.NB;
/**
@@ -56,10 +70,17 @@ public class CommitGraphWriter {
private static final int GENERATION_NUMBER_MAX = 0x3FFFFFFF;
+ private static final int MAX_CHANGED_PATHS = 512;
+
+ private static final PathDiffCalculator PATH_DIFF_CALCULATOR
+ = new PathDiffCalculator();
+
private final int hashsz;
private final GraphCommits graphCommits;
+ private final boolean generateChangedPathFilters;
+
/**
* Create commit-graph writer for these commits.
*
@@ -67,8 +88,22 @@ public class CommitGraphWriter {
* the commits which will be writen to the commit-graph.
*/
public CommitGraphWriter(@NonNull GraphCommits graphCommits) {
+ this(graphCommits, false);
+ }
+
+ /**
+ * Create commit-graph writer for these commits.
+ *
+ * @param graphCommits
+ * the commits which will be writen to the commit-graph.
+ * @param generateChangedPathFilters
+ * whether changed path filters are generated
+ */
+ public CommitGraphWriter(@NonNull GraphCommits graphCommits,
+ boolean generateChangedPathFilters) {
this.graphCommits = graphCommits;
this.hashsz = OBJECT_ID_LENGTH;
+ this.generateChangedPathFilters = generateChangedPathFilters;
}
/**
@@ -80,37 +115,46 @@ public class CommitGraphWriter {
* output stream of commit-graph data. The stream should be
* buffered by the caller. The caller is responsible for closing
* the stream.
+ * @return statistics gathered during the run
* @throws IOException
+ * if an error occurred
*/
- public void write(@NonNull ProgressMonitor monitor,
+ public Stats write(@NonNull ProgressMonitor monitor,
@NonNull OutputStream commitGraphStream) throws IOException {
if (graphCommits.size() == 0) {
- return;
+ return Stats.EMPTY;
}
- List<ChunkHeader> chunks = createChunks();
- long writeCount = 256 + 2 * graphCommits.size()
- + graphCommits.getExtraEdgeCnt();
- monitor.beginTask(
- MessageFormat.format(JGitText.get().writingOutCommitGraph,
- Integer.valueOf(chunks.size())),
- (int) writeCount);
+ BloomFilterChunks bloomFilterChunks = generateChangedPathFilters
+ ? computeBloomFilterChunks(monitor)
+ : null;
+ List<ChunkHeader> chunks = new ArrayList<>();
+ chunks.addAll(createCoreChunks(hashsz, graphCommits));
+ chunks.addAll(createBloomFilterChunkHeaders(bloomFilterChunks));
+ chunks = Collections.unmodifiableList(chunks);
+ long expectedSize = calculateExpectedSize(chunks);
try (CancellableDigestOutputStream out = new CancellableDigestOutputStream(
monitor, commitGraphStream)) {
writeHeader(out, chunks.size());
writeChunkLookup(out, chunks);
- writeChunks(monitor, out, chunks);
+ writeChunks(out, chunks);
writeCheckSum(out);
+ if (expectedSize != out.length()) {
+ throw new IllegalStateException(String.format(
+ JGitText.get().commitGraphUnexpectedSize,
+ Long.valueOf(expectedSize),
+ Long.valueOf(out.length())));
+ }
} catch (InterruptedIOException e) {
throw new IOException(JGitText.get().commitGraphWritingCancelled,
e);
- } finally {
- monitor.endTask();
}
+ return Stats.from(bloomFilterChunks);
}
- private List<ChunkHeader> createChunks() {
+ private static List<ChunkHeader> createCoreChunks(int hashsz,
+ GraphCommits graphCommits) {
List<ChunkHeader> chunks = new ArrayList<>();
chunks.add(new ChunkHeader(CHUNK_ID_OID_FANOUT, GRAPH_FANOUT_SIZE));
chunks.add(new ChunkHeader(CHUNK_ID_OID_LOOKUP,
@@ -121,7 +165,25 @@ public class CommitGraphWriter {
chunks.add(new ChunkHeader(CHUNK_ID_EXTRA_EDGE_LIST,
graphCommits.getExtraEdgeCnt() * 4));
}
- return chunks;
+ return Collections.unmodifiableList(chunks);
+ }
+
+ private static List<ChunkHeader> createBloomFilterChunkHeaders(
+ @Nullable BloomFilterChunks bloomFilterChunks) {
+ List<ChunkHeader> chunks = new ArrayList<>();
+ if (bloomFilterChunks != null) {
+ chunks.add(new ChunkHeader(CHUNK_ID_BLOOM_FILTER_INDEX,
+ bloomFilterChunks.index));
+ chunks.add(new ChunkHeader(CHUNK_ID_BLOOM_FILTER_DATA,
+ bloomFilterChunks.data));
+ }
+ return Collections.unmodifiableList(chunks);
+ }
+
+ private static long calculateExpectedSize(List<ChunkHeader> chunks) {
+ int chunkLookup = (chunks.size() + 1) * CHUNK_LOOKUP_WIDTH;
+ long chunkContent = chunks.stream().mapToLong(c -> c.size).sum();
+ return /* header */ 8 + chunkLookup + chunkContent + /* CRC */ 20;
}
private void writeHeader(CancellableDigestOutputStream out, int numChunks)
@@ -138,7 +200,7 @@ public class CommitGraphWriter {
private void writeChunkLookup(CancellableDigestOutputStream out,
List<ChunkHeader> chunks) throws IOException {
int numChunks = chunks.size();
- long chunkOffset = 8 + (numChunks + 1) * CHUNK_LOOKUP_WIDTH;
+ long chunkOffset = 8 + (numChunks + 1L) * CHUNK_LOOKUP_WIDTH;
byte[] buffer = new byte[CHUNK_LOOKUP_WIDTH];
for (ChunkHeader chunk : chunks) {
NB.encodeInt32(buffer, 0, chunk.id);
@@ -151,9 +213,8 @@ public class CommitGraphWriter {
out.write(buffer);
}
- private void writeChunks(ProgressMonitor monitor,
- CancellableDigestOutputStream out, List<ChunkHeader> chunks)
- throws IOException {
+ private void writeChunks(CancellableDigestOutputStream out,
+ List<ChunkHeader> chunks) throws IOException {
for (ChunkHeader chunk : chunks) {
int chunkId = chunk.id;
@@ -165,11 +226,22 @@ public class CommitGraphWriter {
writeOidLookUp(out);
break;
case CHUNK_ID_COMMIT_DATA:
- writeCommitData(monitor, out);
+ writeCommitData(out);
break;
case CHUNK_ID_EXTRA_EDGE_LIST:
writeExtraEdges(out);
break;
+ case CHUNK_ID_BLOOM_FILTER_INDEX:
+ case CHUNK_ID_BLOOM_FILTER_DATA:
+ if (!chunk.data.isPresent()) {
+ throw new IllegalStateException(
+ "data for this chunk must be precomputed"); //$NON-NLS-1$
+ }
+ chunk.data.get().writeTo(out);
+ break;
+ default:
+ throw new IllegalStateException(
+ "Don't know how to write chunk " + chunkId); //$NON-NLS-1$
}
}
}
@@ -193,7 +265,6 @@ public class CommitGraphWriter {
for (int n : fanout) {
NB.encodeInt32(tmp, 0, n);
out.write(tmp, 0, 4);
- out.getWriteMonitor().update(1);
}
}
@@ -204,13 +275,15 @@ public class CommitGraphWriter {
for (RevCommit c : graphCommits) {
c.copyRawTo(tmp, 0);
out.write(tmp, 0, hashsz);
- out.getWriteMonitor().update(1);
}
}
- private void writeCommitData(ProgressMonitor monitor,
- CancellableDigestOutputStream out) throws IOException {
+ private void writeCommitData(CancellableDigestOutputStream out)
+ throws IOException {
+ ProgressMonitor monitor = out.getWriteMonitor();
int[] generations = computeGenerationNumbers(monitor);
+ monitor.beginTask(JGitText.get().writingOutCommitGraph,
+ graphCommits.size());
int num = 0;
byte[] tmp = new byte[hashsz + COMMIT_DATA_WIDTH];
int i = 0;
@@ -248,9 +321,10 @@ public class CommitGraphWriter {
NB.encodeInt32(tmp, hashsz + 12, packedDate[1]);
out.write(tmp);
- out.getWriteMonitor().update(1);
+ monitor.update(1);
i++;
}
+ monitor.endTask();
}
private int[] computeGenerationNumbers(ProgressMonitor monitor)
@@ -266,10 +340,10 @@ public class CommitGraphWriter {
continue;
}
- Stack<RevCommit> commitStack = new Stack<>();
+ ArrayDeque<RevCommit> commitStack = new ArrayDeque<>();
commitStack.push(cmit);
- while (!commitStack.empty()) {
+ while (!commitStack.isEmpty()) {
int maxGeneration = 0;
boolean allParentComputed = true;
RevCommit current = commitStack.peek();
@@ -304,6 +378,54 @@ public class CommitGraphWriter {
return generations;
}
+ private BloomFilterChunks computeBloomFilterChunks(ProgressMonitor monitor)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ CorruptObjectException, IOException {
+
+ ByteArrayOutputStream index = new ByteArrayOutputStream();
+ ByteArrayOutputStream data = new ByteArrayOutputStream();
+ long filtersReused = 0;
+ long filtersComputed =0;
+
+ // Allocate scratch buffer for converting integers into
+ // big-endian bytes.
+ byte[] scratch = new byte[4];
+
+ NB.encodeInt32(scratch, 0, 1); // version 1
+ data.write(scratch);
+ NB.encodeInt32(scratch, 0, ChangedPathFilter.PATH_HASH_COUNT);
+ data.write(scratch);
+ NB.encodeInt32(scratch, 0, ChangedPathFilter.BITS_PER_ENTRY);
+ data.write(scratch);
+ int dataHeaderSize = data.size();
+
+ try (RevWalk rw = new RevWalk(graphCommits.getObjectReader())) {
+ monitor.beginTask(JGitText.get().computingPathBloomFilters,
+ graphCommits.size());
+ for (RevCommit cmit : graphCommits) {
+ ChangedPathFilter cpf = cmit.getChangedPathFilter(rw);
+ if (cpf != null) {
+ filtersReused++;
+ } else {
+ filtersComputed++;
+ Optional<HashSet<ByteBuffer>> paths = PATH_DIFF_CALCULATOR
+ .changedPaths(graphCommits.getObjectReader(), cmit);
+ if (paths.isEmpty()) {
+ cpf = ChangedPathFilter.FULL;
+ } else {
+ cpf = ChangedPathFilter.fromPaths(paths.get());
+ }
+ }
+ cpf.writeTo(data);
+ NB.encodeInt32(scratch, 0, data.size() - dataHeaderSize);
+ index.write(scratch);
+ monitor.update(1);
+ }
+ monitor.endTask();
+ return new BloomFilterChunks(index, data, filtersReused, filtersComputed);
+ }
+ }
+
private void writeExtraEdges(CancellableDigestOutputStream out)
throws IOException {
byte[] tmp = new byte[4];
@@ -319,20 +441,129 @@ public class CommitGraphWriter {
}
NB.encodeInt32(tmp, 0, edgeValue);
out.write(tmp);
- out.getWriteMonitor().update(1);
}
}
}
}
+ // Visible for testing
+ static class PathDiffCalculator {
+
+ // Walk steps in the last invocation of changedPaths
+ int stepCounter;
+
+ Optional<HashSet<ByteBuffer>> changedPaths(
+ ObjectReader or, RevCommit cmit) throws MissingObjectException,
+ IncorrectObjectTypeException, CorruptObjectException, IOException {
+ stepCounter = 0;
+ HashSet<ByteBuffer> paths = new HashSet<>();
+ try (TreeWalk walk = new TreeWalk(null, or)) {
+ walk.setRecursive(true);
+ walk.setFilter(TreeFilter.ANY_DIFF);
+ if (cmit.getParentCount() == 0) {
+ walk.addTree(new EmptyTreeIterator());
+ } else {
+ walk.addTree(cmit.getParent(0).getTree());
+ }
+ walk.addTree(cmit.getTree());
+ while (walk.next()) {
+ stepCounter += 1;
+ byte[] rawPath = walk.getRawPath();
+ paths.add(ByteBuffer.wrap(rawPath));
+ for (int i = 0; i < rawPath.length; i++) {
+ if (rawPath[i] == '/') {
+ paths.add(ByteBuffer.wrap(rawPath, 0, i));
+ }
+ if (paths.size() > MAX_CHANGED_PATHS) {
+ return Optional.empty();
+ }
+ }
+ }
+ }
+ return Optional.of(paths);
+ }
+ }
+
private static class ChunkHeader {
final int id;
final long size;
+ final Optional<ByteArrayOutputStream> data;
+
public ChunkHeader(int id, long size) {
this.id = id;
this.size = size;
+ this.data = Optional.empty();
+ }
+
+ ChunkHeader(int id, ByteArrayOutputStream data) {
+ this.id = id;
+ this.size = data.size();
+ this.data = Optional.of(data);
+ }
+ }
+
+ private static class BloomFilterChunks {
+ final ByteArrayOutputStream index;
+
+ final ByteArrayOutputStream data;
+
+ final long filtersReused;
+
+ final long filtersComputed;
+
+ BloomFilterChunks(ByteArrayOutputStream index,
+ ByteArrayOutputStream data,
+ long filtersReused,
+ long filtersComputed) {
+ this.index = index;
+ this.data = data;
+ this.filtersReused = filtersReused;
+ this.filtersComputed = filtersComputed;
+ }
+ }
+
+ /**
+ * Statistics collected during a single commit graph write.
+ */
+ public static class Stats {
+
+ static final Stats EMPTY = new Stats();
+
+ static final Stats from(@Nullable BloomFilterChunks bloomFilterChunks) {
+ Stats stats = new Stats();
+ if (bloomFilterChunks != null) {
+ stats.changedPathFiltersComputed = bloomFilterChunks.filtersComputed;
+ stats.changedPathFiltersReused = bloomFilterChunks.filtersReused;
+ }
+ return stats;
+ }
+
+ private Stats() {}
+
+ private long changedPathFiltersReused = 0;
+
+ private long changedPathFiltersComputed = 0;
+
+ /**
+ * Returns the number of existing changed path filters that were reused
+ * when writing, for statistical purposes.
+ *
+ * @return count of changed path filters
+ */
+ public long getChangedPathFiltersReused() {
+ return changedPathFiltersReused;
+ }
+
+ /**
+ * Returns the number of changed path filters that were computed from
+ * scratch, for statistical purposes.
+ *
+ * @return count of changed path filters
+ */
+ public long getChangedPathFiltersComputed() {
+ return changedPathFiltersComputed;
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/GraphChangedPathFilterData.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/GraphChangedPathFilterData.java
new file mode 100644
index 0000000000..738a42a01d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/GraphChangedPathFilterData.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023, Google LLC.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.commitgraph;
+
+import org.eclipse.jgit.util.NB;
+
+/**
+ * Represents the BIDX and BDAT data found in a commit graph file.
+ */
+class GraphChangedPathFilterData {
+
+ private static final int BIDX_BYTES_PER_ENTRY = 4;
+
+ private static final int BDAT_HEADER_BYTES = 12;
+
+ private final byte[] bloomFilterIndex;
+
+ private final byte[] bloomFilterData;
+
+ /**
+ * Initialize the GraphChangedPathFilterData.
+ *
+ * @param bloomFilterIndex
+ * content of BIDX chunk, if it exists
+ * @param bloomFilterData
+ * content of BDAT chunk, if it exists
+ */
+ GraphChangedPathFilterData(byte[] bloomFilterIndex,
+ byte[] bloomFilterData) {
+
+ if ((bloomFilterIndex == null) != (bloomFilterData == null)) {
+ bloomFilterIndex = null;
+ bloomFilterData = null;
+ }
+ if (bloomFilterData != null
+ && (NB.decodeUInt32(bloomFilterData,
+ 4) != ChangedPathFilter.PATH_HASH_COUNT
+ || NB.decodeUInt32(bloomFilterData,
+ 8) != ChangedPathFilter.BITS_PER_ENTRY)) {
+ bloomFilterIndex = null;
+ bloomFilterData = null;
+ }
+
+ this.bloomFilterIndex = bloomFilterIndex;
+ this.bloomFilterData = bloomFilterData;
+ }
+
+ ChangedPathFilter getChangedPathFilter(int graphPos) {
+ if (bloomFilterIndex == null) {
+ return null;
+ }
+ int priorCumul = graphPos == 0 ? 0
+ : NB.decodeInt32(bloomFilterIndex,
+ graphPos * BIDX_BYTES_PER_ENTRY - BIDX_BYTES_PER_ENTRY);
+ int cumul = NB.decodeInt32(bloomFilterIndex, graphPos * BIDX_BYTES_PER_ENTRY);
+ return ChangedPathFilter.fromFile(bloomFilterData,
+ priorCumul + BDAT_HEADER_BYTES,
+ cumul - priorCumul);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/GraphCommits.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/GraphCommits.java
index ccf6d0e66a..c77d950377 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/GraphCommits.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/GraphCommits.java
@@ -23,6 +23,7 @@ import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdOwnerMap;
+import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
@@ -56,6 +57,7 @@ public class GraphCommits implements Iterable<RevCommit> {
* @return the commits' collection which are used by the commit-graph
* writer. Never null.
* @throws IOException
+ * if an error occurred
*/
public static GraphCommits fromWalk(ProgressMonitor pm,
@NonNull Set<? extends ObjectId> wants, @NonNull RevWalk walk)
@@ -78,7 +80,7 @@ public class GraphCommits implements Iterable<RevCommit> {
commits.add(c);
}
pm.endTask();
- return new GraphCommits(commits);
+ return new GraphCommits(commits, walk.getObjectReader());
}
private final List<RevCommit> sortedCommits;
@@ -87,13 +89,17 @@ public class GraphCommits implements Iterable<RevCommit> {
private final int extraEdgeCnt;
+ private final ObjectReader objectReader;
+
/**
* Initialize the GraphCommits.
*
* @param commits
* list of commits with their headers already parsed.
+ * @param objectReader
+ * object reader
*/
- private GraphCommits(List<RevCommit> commits) {
+ private GraphCommits(List<RevCommit> commits, ObjectReader objectReader) {
Collections.sort(commits); // sorted by name
sortedCommits = commits;
commitPosMap = new ObjectIdOwnerMap<>();
@@ -106,6 +112,7 @@ public class GraphCommits implements Iterable<RevCommit> {
commitPosMap.add(new CommitWithPosition(c, i));
}
this.extraEdgeCnt = cnt;
+ this.objectReader = objectReader;
}
int getOidPosition(RevCommit c) throws MissingObjectException {
@@ -124,7 +131,10 @@ public class GraphCommits implements Iterable<RevCommit> {
return sortedCommits.size();
}
- /** {@inheritDoc} */
+ ObjectReader getObjectReader() {
+ return objectReader;
+ }
+
@Override
public Iterator<RevCommit> iterator() {
return sortedCommits.iterator();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/GraphObjectIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/GraphObjectIndex.java
index b0df46732e..a15602a168 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/GraphObjectIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/commitgraph/GraphObjectIndex.java
@@ -56,7 +56,7 @@ class GraphObjectIndex {
long uint32;
for (int k = 0; k < table.length; k++) {
uint32 = NB.decodeUInt32(oidFanout, k * 4);
- if (table[k] > Integer.MAX_VALUE) {
+ if (uint32 > Integer.MAX_VALUE) {
throw new CommitGraphFormatException(
JGitText.get().commitGraphFileIsTooLargeForJgit);
}
@@ -80,7 +80,7 @@ class GraphObjectIndex {
if (levelOne > 0) {
low = fanoutTable[levelOne - 1];
}
- do {
+ while (low < high) {
int mid = (low + high) >>> 1;
int pos = objIdOffset(mid);
int cmp = id.compareTo(oidLookup, pos);
@@ -91,7 +91,7 @@ class GraphObjectIndex {
} else {
low = mid + 1;
}
- } while (low < high);
+ }
return -1;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/AggregatedBlockCacheStats.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/AggregatedBlockCacheStats.java
new file mode 100644
index 0000000000..295b702fa7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/AggregatedBlockCacheStats.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2024, Google LLC and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import static org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheTable.BlockCacheStats;
+
+import java.util.List;
+
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+
+/**
+ * Aggregates values for all given {@link BlockCacheStats}.
+ */
+class AggregatedBlockCacheStats implements BlockCacheStats {
+ private final List<BlockCacheStats> blockCacheStats;
+
+ static BlockCacheStats fromStatsList(
+ List<BlockCacheStats> blockCacheStats) {
+ if (blockCacheStats.size() == 1) {
+ return blockCacheStats.get(0);
+ }
+ return new AggregatedBlockCacheStats(blockCacheStats);
+ }
+
+ private AggregatedBlockCacheStats(List<BlockCacheStats> blockCacheStats) {
+ this.blockCacheStats = blockCacheStats;
+ }
+
+ @Override
+ public String getName() {
+ return AggregatedBlockCacheStats.class.getName();
+ }
+
+ @Override
+ public long[] getCurrentSize() {
+ long[] sums = emptyPackStats();
+ for (BlockCacheStats blockCacheStatsEntry : blockCacheStats) {
+ sums = add(sums, blockCacheStatsEntry.getCurrentSize());
+ }
+ return sums;
+ }
+
+ @Override
+ public long[] getHitCount() {
+ long[] sums = emptyPackStats();
+ for (BlockCacheStats blockCacheStatsEntry : blockCacheStats) {
+ sums = add(sums, blockCacheStatsEntry.getHitCount());
+ }
+ return sums;
+ }
+
+ @Override
+ public long[] getMissCount() {
+ long[] sums = emptyPackStats();
+ for (BlockCacheStats blockCacheStatsEntry : blockCacheStats) {
+ sums = add(sums, blockCacheStatsEntry.getMissCount());
+ }
+ return sums;
+ }
+
+ @Override
+ public long[] getTotalRequestCount() {
+ long[] sums = emptyPackStats();
+ for (BlockCacheStats blockCacheStatsEntry : blockCacheStats) {
+ sums = add(sums, blockCacheStatsEntry.getTotalRequestCount());
+ }
+ return sums;
+ }
+
+ @Override
+ public long[] getHitRatio() {
+ long[] hit = getHitCount();
+ long[] miss = getMissCount();
+ long[] ratio = new long[Math.max(hit.length, miss.length)];
+ for (int i = 0; i < ratio.length; i++) {
+ if (i >= hit.length) {
+ ratio[i] = 0;
+ } else if (i >= miss.length) {
+ ratio[i] = 100;
+ } else {
+ long total = hit[i] + miss[i];
+ ratio[i] = total == 0 ? 0 : hit[i] * 100 / total;
+ }
+ }
+ return ratio;
+ }
+
+ @Override
+ public long[] getEvictions() {
+ long[] sums = emptyPackStats();
+ for (BlockCacheStats blockCacheStatsEntry : blockCacheStats) {
+ sums = add(sums, blockCacheStatsEntry.getEvictions());
+ }
+ return sums;
+ }
+
+ private static long[] emptyPackStats() {
+ return new long[PackExt.values().length];
+ }
+
+ private static long[] add(long[] first, long[] second) {
+ long[] sums = new long[Integer.max(first.length, second.length)];
+ int i;
+ for (i = 0; i < Integer.min(first.length, second.length); i++) {
+ sums[i] = first[i] + second[i];
+ }
+ for (int j = i; j < first.length; j++) {
+ sums[j] = first[i];
+ }
+ for (int j = i; j < second.length; j++) {
+ sums[j] = second[i];
+ }
+ return sums;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedEvent.java
index cdfd3e9592..68fc296d3e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedEvent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BeforeDfsPackIndexLoadedEvent.java
@@ -41,13 +41,11 @@ public class BeforeDfsPackIndexLoadedEvent
return pack;
}
- /** {@inheritDoc} */
@Override
public Class<BeforeDfsPackIndexLoadedListener> getListenerType() {
return BeforeDfsPackIndexLoadedListener.class;
}
- /** {@inheritDoc} */
@Override
public void dispatch(BeforeDfsPackIndexLoadedListener listener) {
listener.onBeforeDfsPackIndexLoaded(this);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BlockBasedFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BlockBasedFile.java
index b1b09baf20..ab07475663 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BlockBasedFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/BlockBasedFile.java
@@ -100,7 +100,9 @@ abstract class BlockBasedFile {
DfsBlock getOrLoadBlock(long pos, DfsReader ctx) throws IOException {
try (LazyChannel c = new LazyChannel(ctx, desc, ext)) {
- return cache.getOrLoad(this, pos, ctx, c);
+ DfsBlock block = cache.getOrLoad(this, pos, ctx, c);
+ ctx.emitBlockLoad(this, pos, block);
+ return block;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ClockBlockCacheTable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ClockBlockCacheTable.java
new file mode 100644
index 0000000000..587d482583
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ClockBlockCacheTable.java
@@ -0,0 +1,575 @@
+/*
+ * Copyright (c) 2024, Google LLC and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Consumer;
+import java.util.stream.LongStream;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.dfs.DfsBlockCache.ReadableChannelSupplier;
+import org.eclipse.jgit.internal.storage.dfs.DfsBlockCache.Ref;
+import org.eclipse.jgit.internal.storage.dfs.DfsBlockCache.RefLoader;
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+
+/**
+ * Default implementation of the {@link DfsBlockCacheTable}.
+ * <p>
+ * This cache implements a clock replacement algorithm, giving each block at
+ * least one chance to have been accessed during a sweep of the cache to save
+ * itself from eviction. The number of swipe chances is configurable per pack
+ * extension.
+ * <p>
+ * Entities created by the cache are held under hard references, preventing the
+ * Java VM from clearing anything. Blocks are discarded by the replacement
+ * algorithm when adding a new block would cause the cache to exceed its
+ * configured maximum size.
+ * <p>
+ * Whenever a cache miss occurs, loading is invoked by exactly one thread for
+ * the given <code>(DfsStreamKey,position)</code> key tuple. This is ensured by
+ * an array of locks, with the tuple hashed to a lock instance.
+ * <p>
+ * The internal hash table does not expand at runtime, instead it is fixed in
+ * size at cache creation time. The internal lock table used to gate load
+ * invocations is also fixed in size.
+ */
+final class ClockBlockCacheTable implements DfsBlockCacheTable {
+ /**
+ * Table name.
+ */
+ private final String name;
+
+ /** Number of entries in {@link #table}. */
+ private final int tableSize;
+
+ /** Maximum number of bytes the cache should hold. */
+ private final long maxBytes;
+
+ /**
+ * Used to reserve space for blocks.
+ * <p>
+ * The value for blockSize must be a power of 2.
+ */
+ private final int blockSize;
+
+ private final Hash hash;
+
+ /** Hash bucket directory; entries are chained below. */
+ private final AtomicReferenceArray<HashEntry> table;
+
+ /**
+ * Locks to prevent concurrent loads for same (PackFile,position) block. The
+ * number of locks is {@link DfsBlockCacheConfig#getConcurrencyLevel()} to
+ * cap the overall concurrent block loads.
+ */
+ private final ReentrantLock[] loadLocks;
+
+ /**
+ * A separate pool of locks per pack extension to prevent concurrent loads
+ * for same index or bitmap from PackFile.
+ */
+ private final ReentrantLock[][] refLocks;
+
+ /** Protects the clock and its related data. */
+ private final ReentrantLock clockLock;
+
+ /** Current position of the clock. */
+ private Ref clockHand;
+
+ private final DfsBlockCacheStats dfsBlockCacheStats;
+
+ /**
+ * A consumer of object reference lock wait time milliseconds. May be used
+ * to build a metric.
+ */
+ private final Consumer<Long> refLockWaitTime;
+
+ /** Consumer of loading and eviction events of indexes. */
+ private final DfsBlockCacheConfig.IndexEventConsumer indexEventConsumer;
+
+ /** Stores timestamps of the last eviction of indexes. */
+ private final Map<EvictKey, Long> indexEvictionMap = new ConcurrentHashMap<>();
+
+ ClockBlockCacheTable(DfsBlockCacheConfig cfg) {
+ this.tableSize = tableSize(cfg);
+ if (tableSize < 1) {
+ throw new IllegalArgumentException(
+ JGitText.get().tSizeMustBeGreaterOrEqual1);
+ }
+ int concurrencyLevel = cfg.getConcurrencyLevel();
+ this.maxBytes = cfg.getBlockLimit();
+ this.blockSize = cfg.getBlockSize();
+ int blockSizeShift = Integer.numberOfTrailingZeros(blockSize);
+ this.hash = new Hash(blockSizeShift);
+ table = new AtomicReferenceArray<>(tableSize);
+
+ loadLocks = new ReentrantLock[concurrencyLevel];
+ for (int i = 0; i < loadLocks.length; i++) {
+ loadLocks[i] = new ReentrantLock(/* fair= */ true);
+ }
+ refLocks = new ReentrantLock[PackExt.values().length][concurrencyLevel];
+ for (int i = 0; i < PackExt.values().length; i++) {
+ for (int j = 0; j < concurrencyLevel; ++j) {
+ refLocks[i][j] = new ReentrantLock(/* fair= */ true);
+ }
+ }
+
+ clockLock = new ReentrantLock(/* fair= */ true);
+ String none = ""; //$NON-NLS-1$
+ clockHand = new Ref<>(
+ DfsStreamKey.of(new DfsRepositoryDescription(none), none, null),
+ -1, 0, null);
+ clockHand.next = clockHand;
+
+ this.name = cfg.getName();
+ this.dfsBlockCacheStats = new DfsBlockCacheStats(this.name);
+ this.refLockWaitTime = cfg.getRefLockWaitTimeConsumer();
+ this.indexEventConsumer = cfg.getIndexEventConsumer();
+ }
+
+ @Override
+ public List<BlockCacheStats> getBlockCacheStats() {
+ return List.of(dfsBlockCacheStats);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean hasBlock0(DfsStreamKey key) {
+ HashEntry e1 = table.get(slot(key, 0));
+ DfsBlock v = scan(e1, key, 0);
+ return v != null && v.contains(key, 0);
+ }
+
+ @Override
+ public DfsBlock getOrLoad(BlockBasedFile file, long position, DfsReader ctx,
+ ReadableChannelSupplier fileChannel) throws IOException {
+ final long requestedPosition = position;
+ position = file.alignToBlock(position);
+
+ DfsStreamKey key = file.key;
+ int slot = slot(key, position);
+ HashEntry e1 = table.get(slot);
+ DfsBlock v = scan(e1, key, position);
+ if (v != null && v.contains(key, requestedPosition)) {
+ ctx.stats.blockCacheHit++;
+ dfsBlockCacheStats.incrementHit(key);
+ return v;
+ }
+
+ reserveSpace(blockSize, key);
+ ReentrantLock regionLock = lockFor(key, position);
+ regionLock.lock();
+ try {
+ HashEntry e2 = table.get(slot);
+ if (e2 != e1) {
+ v = scan(e2, key, position);
+ if (v != null) {
+ ctx.stats.blockCacheHit++;
+ dfsBlockCacheStats.incrementHit(key);
+ creditSpace(blockSize, key);
+ return v;
+ }
+ }
+
+ dfsBlockCacheStats.incrementMiss(key);
+ boolean credit = true;
+ try {
+ v = file.readOneBlock(position, ctx, fileChannel.get());
+ credit = false;
+ } finally {
+ if (credit) {
+ creditSpace(blockSize, key);
+ }
+ }
+ if (position != v.start) {
+ // The file discovered its blockSize and adjusted.
+ position = v.start;
+ slot = slot(key, position);
+ e2 = table.get(slot);
+ }
+
+ Ref<DfsBlock> ref = new Ref<>(key, position, v.size(), v);
+ ref.markHotter();
+ for (;;) {
+ HashEntry n = new HashEntry(HashEntry.clean(e2), ref);
+ if (table.compareAndSet(slot, e2, n)) {
+ break;
+ }
+ e2 = table.get(slot);
+ }
+ addToClock(ref, blockSize - v.size());
+ } finally {
+ regionLock.unlock();
+ }
+
+ // If the block size changed from the default, it is possible the
+ // block
+ // that was loaded is the wrong block for the requested position.
+ if (v.contains(file.key, requestedPosition)) {
+ return v;
+ }
+ return getOrLoad(file, requestedPosition, ctx, fileChannel);
+ }
+
+ @Override
+ public <T> Ref<T> getOrLoadRef(DfsStreamKey key, long position,
+ RefLoader<T> loader) throws IOException {
+ long start = System.nanoTime();
+ int slot = slot(key, position);
+ HashEntry e1 = table.get(slot);
+ Ref<T> ref = scanRef(e1, key, position);
+ if (ref != null) {
+ dfsBlockCacheStats.incrementHit(key);
+ reportIndexRequested(ref, true /* cacheHit= */, start);
+ return ref;
+ }
+
+ ReentrantLock regionLock = lockForRef(key);
+ long lockStart = System.currentTimeMillis();
+ regionLock.lock();
+ try {
+ HashEntry e2 = table.get(slot);
+ if (e2 != e1) {
+ ref = scanRef(e2, key, position);
+ if (ref != null) {
+ dfsBlockCacheStats.incrementHit(key);
+ reportIndexRequested(ref, true /* cacheHit= */, start);
+ return ref;
+ }
+ }
+
+ if (refLockWaitTime != null) {
+ refLockWaitTime.accept(
+ Long.valueOf(System.currentTimeMillis() - lockStart));
+ }
+ dfsBlockCacheStats.incrementMiss(key);
+ ref = loader.load();
+ ref.markHotter();
+ // Reserve after loading to get the size of the object
+ reserveSpace(ref.size, key);
+ for (;;) {
+ HashEntry n = new HashEntry(HashEntry.clean(e2), ref);
+ if (table.compareAndSet(slot, e2, n)) {
+ break;
+ }
+ e2 = table.get(slot);
+ }
+ addToClock(ref, 0);
+ } finally {
+ regionLock.unlock();
+ }
+ reportIndexRequested(ref, /* cacheHit= */ false, start);
+ return ref;
+ }
+
+ @Override
+ public void put(DfsBlock v) {
+ put(v.stream, v.start, v.size(), v);
+ }
+
+ @Override
+ public <T> Ref<T> put(DfsStreamKey key, long pos, long size, T v) {
+ int slot = slot(key, pos);
+ HashEntry e1 = table.get(slot);
+ Ref<T> ref = scanRef(e1, key, pos);
+ if (ref != null) {
+ return ref;
+ }
+
+ reserveSpace(size, key);
+ ReentrantLock regionLock = lockFor(key, pos);
+ regionLock.lock();
+ try {
+ HashEntry e2 = table.get(slot);
+ if (e2 != e1) {
+ ref = scanRef(e2, key, pos);
+ if (ref != null) {
+ creditSpace(size, key);
+ return ref;
+ }
+ }
+
+ ref = new Ref<>(key, pos, size, v);
+ ref.markHotter();
+ for (;;) {
+ HashEntry n = new HashEntry(HashEntry.clean(e2), ref);
+ if (table.compareAndSet(slot, e2, n)) {
+ break;
+ }
+ e2 = table.get(slot);
+ }
+ addToClock(ref, 0);
+ } finally {
+ regionLock.unlock();
+ }
+ return ref;
+ }
+
+ @Override
+ public <T> Ref<T> putRef(DfsStreamKey key, long size, T v) {
+ return put(key, 0, size, v);
+ }
+
+ @Override
+ public boolean contains(DfsStreamKey key, long position) {
+ return scan(table.get(slot(key, position)), key, position) != null;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T get(DfsStreamKey key, long position) {
+ T val = (T) scan(table.get(slot(key, position)), key, position);
+ if (val == null) {
+ dfsBlockCacheStats.incrementMiss(key);
+ } else {
+ dfsBlockCacheStats.incrementHit(key);
+ }
+ return val;
+ }
+
+ private int slot(DfsStreamKey key, long position) {
+ return (hash.hash(key.hash, position) >>> 1) % tableSize;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void reserveSpace(long reserve, DfsStreamKey key) {
+ clockLock.lock();
+ try {
+ long live = LongStream.of(dfsBlockCacheStats.getCurrentSize()).sum()
+ + reserve;
+ if (maxBytes < live) {
+ Ref prev = clockHand;
+ Ref hand = clockHand.next;
+ do {
+ if (hand.isHot()) {
+ // Value was recently touched. Cache is still hot so
+ // give it another chance, but cool it down a bit.
+ hand.markColder();
+ prev = hand;
+ hand = hand.next;
+ continue;
+ } else if (prev == hand) {
+ break;
+ }
+
+ // No recent access since last scan, kill
+ // value and remove from clock.
+ Ref dead = hand;
+ hand = hand.next;
+ prev.next = hand;
+ dead.next = null;
+ dead.value = null;
+ live -= dead.size;
+ dfsBlockCacheStats.addToLiveBytes(dead.key, -dead.size);
+ dfsBlockCacheStats.incrementEvict(dead.key);
+ reportIndexEvicted(dead);
+ } while (maxBytes < live);
+ clockHand = prev;
+ }
+ dfsBlockCacheStats.addToLiveBytes(key, reserve);
+ } finally {
+ clockLock.unlock();
+ }
+ }
+
+ private void creditSpace(long credit, DfsStreamKey key) {
+ clockLock.lock();
+ try {
+ dfsBlockCacheStats.addToLiveBytes(key, -credit);
+ } finally {
+ clockLock.unlock();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void addToClock(Ref ref, long credit) {
+ clockLock.lock();
+ try {
+ if (credit != 0) {
+ dfsBlockCacheStats.addToLiveBytes(ref.key, -credit);
+ }
+ Ref ptr = clockHand;
+ ref.next = ptr.next;
+ ptr.next = ref;
+ clockHand = ref;
+ } finally {
+ clockLock.unlock();
+ }
+ }
+
+ private <T> T scan(HashEntry n, DfsStreamKey key, long position) {
+ Ref<T> r = scanRef(n, key, position);
+ return r != null ? r.get() : null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T> Ref<T> scanRef(HashEntry n, DfsStreamKey key, long position) {
+ for (; n != null; n = n.next) {
+ Ref<T> r = n.ref;
+ if (r.position == position && r.key.equals(key)) {
+ return r.get() != null ? r : null;
+ }
+ }
+ return null;
+ }
+
+ private ReentrantLock lockFor(DfsStreamKey key, long position) {
+ return loadLocks[(hash.hash(key.hash, position) >>> 1)
+ % loadLocks.length];
+ }
+
+ private ReentrantLock lockForRef(DfsStreamKey key) {
+ int slot = (key.hash >>> 1) % refLocks[key.packExtPos].length;
+ return refLocks[key.packExtPos][slot];
+ }
+
+ private void reportIndexRequested(Ref<?> ref, boolean cacheHit,
+ long start) {
+ if (indexEventConsumer == null || !isIndexExtPos(ref.key.packExtPos)) {
+ return;
+ }
+ EvictKey evictKey = createEvictKey(ref);
+ Long prevEvictedTime = indexEvictionMap.get(evictKey);
+ long now = System.nanoTime();
+ long sinceLastEvictionNanos = prevEvictedTime == null ? 0L
+ : now - prevEvictedTime.longValue();
+ indexEventConsumer.acceptRequestedEvent(ref.key.packExtPos, cacheHit,
+ (now - start) / 1000L /* micros */, ref.size,
+ Duration.ofNanos(sinceLastEvictionNanos));
+ }
+
+ private void reportIndexEvicted(Ref<?> dead) {
+ if (indexEventConsumer == null
+ || !indexEventConsumer.shouldReportEvictedEvent()
+ || !isIndexExtPos(dead.key.packExtPos)) {
+ return;
+ }
+ EvictKey evictKey = createEvictKey(dead);
+ Long prevEvictedTime = indexEvictionMap.get(evictKey);
+ long now = System.nanoTime();
+ long sinceLastEvictionNanos = prevEvictedTime == null ? 0L
+ : now - prevEvictedTime.longValue();
+ indexEvictionMap.put(evictKey, Long.valueOf(now));
+ indexEventConsumer.acceptEvictedEvent(dead.key.packExtPos, dead.size,
+ dead.getTotalHitCount(),
+ Duration.ofNanos(sinceLastEvictionNanos));
+ }
+
+ private static final class HashEntry {
+ /** Next entry in the hash table's chain list. */
+ final HashEntry next;
+
+ /** The referenced object. */
+ final Ref ref;
+
+ HashEntry(HashEntry n, Ref r) {
+ next = n;
+ ref = r;
+ }
+
+ private static HashEntry clean(HashEntry top) {
+ while (top != null && top.ref.next == null) {
+ top = top.next;
+ }
+ if (top == null) {
+ return null;
+ }
+ HashEntry n = clean(top.next);
+ return n == top.next ? top : new HashEntry(n, top.ref);
+ }
+ }
+
+ private EvictKey createEvictKey(Ref<?> ref) {
+ return new EvictKey(hash, ref);
+ }
+
+ private static boolean isIndexExtPos(int packExtPos) {
+ return packExtPos == PackExt.INDEX.getPosition()
+ || packExtPos == PackExt.REVERSE_INDEX.getPosition()
+ || packExtPos == PackExt.BITMAP_INDEX.getPosition();
+ }
+
+ private static int tableSize(DfsBlockCacheConfig cfg) {
+ final int wsz = cfg.getBlockSize();
+ final long limit = cfg.getBlockLimit();
+ if (wsz <= 0) {
+ throw new IllegalArgumentException(
+ JGitText.get().invalidWindowSize);
+ }
+ if (limit < wsz) {
+ throw new IllegalArgumentException(
+ JGitText.get().windowSizeMustBeLesserThanLimit);
+ }
+ return (int) Math.min(5 * (limit / wsz) / 2, Integer.MAX_VALUE);
+ }
+
+ private static final class Hash {
+ /**
+ * As {@link #blockSize} is a power of 2, bits to shift for a /
+ * blockSize.
+ */
+ private final int blockSizeShift;
+
+ Hash(int blockSizeShift) {
+ this.blockSizeShift = blockSizeShift;
+ }
+
+ int hash(int packHash, long off) {
+ return packHash + (int) (off >>> blockSizeShift);
+ }
+ }
+
+ private static final class EvictKey {
+ /**
+ * Provides the hash function to be used for this key's hashCode method.
+ */
+ private final Hash hash;
+
+ private final int keyHash;
+
+ private final int packExtPos;
+
+ private final long position;
+
+ EvictKey(Hash hash, Ref<?> ref) {
+ this.hash = hash;
+ keyHash = ref.key.hash;
+ packExtPos = ref.key.packExtPos;
+ position = ref.position;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object instanceof EvictKey) {
+ EvictKey other = (EvictKey) object;
+ return keyHash == other.keyHash
+ && packExtPos == other.packExtPos
+ && position == other.position;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return hash.hash(keyHash, position);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
index 0a02180d7d..f8e0831e1f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java
@@ -11,19 +11,13 @@
package org.eclipse.jgit.internal.storage.dfs;
+import static org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheTable.BlockCacheStats;
+
import java.io.IOException;
-import java.time.Duration;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.atomic.AtomicReferenceArray;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.function.Consumer;
import java.util.stream.LongStream;
-import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.pack.PackExt;
/**
@@ -36,31 +30,14 @@ import org.eclipse.jgit.internal.storage.pack.PackExt;
* reads of only tiny slices of a file, the DfsBlockCache tries to smooth out
* these tiny reads into larger block-sized IO operations.
* <p>
- * Whenever a cache miss occurs, loading is invoked by exactly one thread for
- * the given <code>(DfsStreamKey,position)</code> key tuple. This is ensured by
- * an array of locks, with the tuple hashed to a lock instance.
- * <p>
* Its too expensive during object access to be 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 clock replacement algorithm, giving each block at least
- * one chance to have been accessed during a sweep of the cache to save itself
- * from eviction. The number of swipe chances is configurable per pack
- * extension.
- * <p>
- * Entities created by the cache are held under hard references, preventing the
- * Java VM from clearing anything. Blocks are discarded by the replacement
- * algorithm when adding a new block would cause the cache to exceed its
- * configured maximum size.
+ * typically doesn't yield a corresponding benefit to the application.
* <p>
* The key tuple is passed through to methods as a pair of parameters rather
* than as a single Object, thus reducing the transient memory allocations of
* callers. It is more efficient to avoid the allocation, as we can't be 100%
* sure that a JIT would be able to stack-allocate a key tuple.
- * <p>
- * 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.
*/
public final class DfsBlockCache {
private static volatile DfsBlockCache cache;
@@ -94,24 +71,7 @@ public final class DfsBlockCache {
return cache;
}
- /** Number of entries in {@link #table}. */
- private final int tableSize;
-
- /** Hash bucket directory; entries are chained below. */
- private final AtomicReferenceArray<HashEntry> table;
-
- /**
- * Locks to prevent concurrent loads for same (PackFile,position) block. The
- * number of locks is {@link DfsBlockCacheConfig#getConcurrencyLevel()} to
- * cap the overall concurrent block loads.
- */
- private final ReentrantLock[] loadLocks;
-
- /**
- * A separate pool of locks per pack extension to prevent concurrent loads
- * for same index or bitmap from PackFile.
- */
- private final ReentrantLock[][] refLocks;
+ private final DfsBlockCacheTable dfsBlockCacheTable;
/** Maximum number of bytes the cache should hold. */
private final long maxBytes;
@@ -131,89 +91,21 @@ public final class DfsBlockCache {
*/
private final int blockSize;
- /** As {@link #blockSize} is a power of 2, bits to shift for a / blockSize. */
- private final int blockSizeShift;
-
- /**
- * Number of times a block was found in the cache, per pack file extension.
- */
- private final AtomicReference<AtomicLong[]> statHit;
-
- /**
- * Number of times a block was not found, and had to be loaded, per pack
- * file extension.
- */
- private final AtomicReference<AtomicLong[]> statMiss;
-
- /**
- * Number of blocks evicted due to cache being full, per pack file
- * extension.
- */
- private final AtomicReference<AtomicLong[]> statEvict;
-
- /**
- * Number of bytes currently loaded in the cache, per pack file extension.
- */
- private final AtomicReference<AtomicLong[]> liveBytes;
-
- /** Protects the clock and its related data. */
- private final ReentrantLock clockLock;
-
- /**
- * A consumer of object reference lock wait time milliseconds. May be used to build a metric.
- */
- private final Consumer<Long> refLockWaitTime;
-
- /** Current position of the clock. */
- private Ref clockHand;
-
/** Limits of cache hot count per pack file extension. */
private final int[] cacheHotLimits = new int[PackExt.values().length];
- /** Consumer of loading and eviction events of indexes. */
- private final DfsBlockCacheConfig.IndexEventConsumer indexEventConsumer;
-
- /** Stores timestamps of the last eviction of indexes. */
- private final Map<EvictKey, Long> indexEvictionMap = new ConcurrentHashMap<>();
-
- @SuppressWarnings("unchecked")
private DfsBlockCache(DfsBlockCacheConfig cfg) {
- tableSize = tableSize(cfg);
- if (tableSize < 1) {
- throw new IllegalArgumentException(JGitText.get().tSizeMustBeGreaterOrEqual1);
- }
-
- table = new AtomicReferenceArray<>(tableSize);
- int concurrencyLevel = cfg.getConcurrencyLevel();
- loadLocks = new ReentrantLock[concurrencyLevel];
- for (int i = 0; i < loadLocks.length; i++) {
- loadLocks[i] = new ReentrantLock(true /* fair */);
- }
- refLocks = new ReentrantLock[PackExt.values().length][concurrencyLevel];
- for (int i = 0; i < PackExt.values().length; i++) {
- for (int j = 0; j < concurrencyLevel; ++j) {
- refLocks[i][j] = new ReentrantLock(true /* fair */);
- }
- }
-
maxBytes = cfg.getBlockLimit();
- maxStreamThroughCache = (long) (maxBytes * cfg.getStreamRatio());
blockSize = cfg.getBlockSize();
- blockSizeShift = Integer.numberOfTrailingZeros(blockSize);
+ double streamRatio = cfg.getStreamRatio();
+ maxStreamThroughCache = (long) (maxBytes * streamRatio);
- clockLock = new ReentrantLock(true /* fair */);
- String none = ""; //$NON-NLS-1$
- clockHand = new Ref<>(
- DfsStreamKey.of(new DfsRepositoryDescription(none), none, null),
- -1, 0, null);
- clockHand.next = clockHand;
-
- statHit = new AtomicReference<>(newCounters());
- statMiss = new AtomicReference<>(newCounters());
- statEvict = new AtomicReference<>(newCounters());
- liveBytes = new AtomicReference<>(newCounters());
-
- refLockWaitTime = cfg.getRefLockWaitTimeConsumer();
+ if (!cfg.getPackExtCacheConfigurations().isEmpty()) {
+ dfsBlockCacheTable = PackExtBlockCacheTable
+ .fromBlockCacheConfigs(cfg);
+ } else {
+ dfsBlockCacheTable = new ClockBlockCacheTable(cfg);
+ }
for (int i = 0; i < PackExt.values().length; ++i) {
Integer limit = cfg.getCacheHotMap().get(PackExt.values()[i]);
@@ -223,7 +115,6 @@ public final class DfsBlockCache {
cacheHotLimits[i] = DfsBlockCacheConfig.DEFAULT_CACHE_HOT_MAX;
}
}
- indexEventConsumer = cfg.getIndexEventConsumer();
}
boolean shouldCopyThroughCache(long length) {
@@ -236,7 +127,7 @@ public final class DfsBlockCache {
* @return total number of bytes in the cache, per pack file extension.
*/
public long[] getCurrentSize() {
- return getStatVals(liveBytes);
+ return getAggregatedBlockCacheStats().getCurrentSize();
}
/**
@@ -255,7 +146,7 @@ public final class DfsBlockCache {
* extension.
*/
public long[] getHitCount() {
- return getStatVals(statHit);
+ return getAggregatedBlockCacheStats().getHitCount();
}
/**
@@ -266,7 +157,7 @@ public final class DfsBlockCache {
* extension.
*/
public long[] getMissCount() {
- return getStatVals(statMiss);
+ return getAggregatedBlockCacheStats().getMissCount();
}
/**
@@ -275,16 +166,7 @@ public final class DfsBlockCache {
* @return total number of requests (hit + miss), per pack file extension.
*/
public long[] getTotalRequestCount() {
- AtomicLong[] hit = statHit.get();
- AtomicLong[] miss = statMiss.get();
- long[] cnt = new long[Math.max(hit.length, miss.length)];
- for (int i = 0; i < hit.length; i++) {
- cnt[i] += hit[i].get();
- }
- for (int i = 0; i < miss.length; i++) {
- cnt[i] += miss[i].get();
- }
- return cnt;
+ return getAggregatedBlockCacheStats().getTotalRequestCount();
}
/**
@@ -293,22 +175,7 @@ public final class DfsBlockCache {
* @return hit ratios
*/
public long[] getHitRatio() {
- AtomicLong[] hit = statHit.get();
- AtomicLong[] miss = statMiss.get();
- long[] ratio = new long[Math.max(hit.length, miss.length)];
- for (int i = 0; i < ratio.length; i++) {
- if (i >= hit.length) {
- ratio[i] = 0;
- } else if (i >= miss.length) {
- ratio[i] = 100;
- } else {
- long hitVal = hit[i].get();
- long missVal = miss[i].get();
- long total = hitVal + missVal;
- ratio[i] = total == 0 ? 0 : hitVal * 100 / total;
- }
- }
- return ratio;
+ return getAggregatedBlockCacheStats().getHitRatio();
}
/**
@@ -319,7 +186,18 @@ public final class DfsBlockCache {
* file extension.
*/
public long[] getEvictions() {
- return getStatVals(statEvict);
+ return getAggregatedBlockCacheStats().getEvictions();
+ }
+
+ /**
+ * Get the list of {@link BlockCacheStats} for all underlying caches.
+ * <p>
+ * Useful in monitoring caches with breakdown.
+ *
+ * @return the list of {@link BlockCacheStats} for all underlying caches.
+ */
+ public List<BlockCacheStats> getAllBlockCacheStats() {
+ return dfsBlockCacheTable.getBlockCacheStats();
}
/**
@@ -334,31 +212,13 @@ public final class DfsBlockCache {
* @return true if block 0 (the first block) is in the cache.
*/
public boolean hasBlock0(DfsStreamKey key) {
- HashEntry e1 = table.get(slot(key, 0));
- DfsBlock v = scan(e1, key, 0);
- return v != null && v.contains(key, 0);
- }
-
- private int hash(int packHash, long off) {
- return packHash + (int) (off >>> blockSizeShift);
+ return dfsBlockCacheTable.hasBlock0(key);
}
int getBlockSize() {
return blockSize;
}
- private static int tableSize(DfsBlockCacheConfig cfg) {
- final int wsz = cfg.getBlockSize();
- final long limit = cfg.getBlockLimit();
- if (wsz <= 0) {
- throw new IllegalArgumentException(JGitText.get().invalidWindowSize);
- }
- if (limit < wsz) {
- throw new IllegalArgumentException(JGitText.get().windowSizeMustBeLesserThanLimit);
- }
- return (int) Math.min(5 * (limit / wsz) / 2, Integer.MAX_VALUE);
- }
-
/**
* Look up a cached object, creating and loading it if it doesn't exist.
*
@@ -376,139 +236,11 @@ public final class DfsBlockCache {
*/
DfsBlock getOrLoad(BlockBasedFile file, long position, DfsReader ctx,
ReadableChannelSupplier fileChannel) throws IOException {
- final long requestedPosition = position;
- position = file.alignToBlock(position);
-
- DfsStreamKey key = file.key;
- int slot = slot(key, position);
- HashEntry e1 = table.get(slot);
- DfsBlock v = scan(e1, key, position);
- if (v != null && v.contains(key, requestedPosition)) {
- ctx.stats.blockCacheHit++;
- getStat(statHit, key).incrementAndGet();
- return v;
- }
-
- reserveSpace(blockSize, key);
- ReentrantLock regionLock = lockFor(key, position);
- regionLock.lock();
- try {
- HashEntry e2 = table.get(slot);
- if (e2 != e1) {
- v = scan(e2, key, position);
- if (v != null) {
- ctx.stats.blockCacheHit++;
- getStat(statHit, key).incrementAndGet();
- creditSpace(blockSize, key);
- return v;
- }
- }
-
- getStat(statMiss, key).incrementAndGet();
- boolean credit = true;
- try {
- v = file.readOneBlock(position, ctx, fileChannel.get());
- credit = false;
- } finally {
- if (credit) {
- creditSpace(blockSize, key);
- }
- }
- if (position != v.start) {
- // The file discovered its blockSize and adjusted.
- position = v.start;
- slot = slot(key, position);
- e2 = table.get(slot);
- }
-
- Ref<DfsBlock> ref = new Ref<>(key, position, v.size(), v);
- ref.markHotter();
- for (;;) {
- HashEntry n = new HashEntry(clean(e2), ref);
- if (table.compareAndSet(slot, e2, n)) {
- break;
- }
- e2 = table.get(slot);
- }
- addToClock(ref, blockSize - v.size());
- } finally {
- regionLock.unlock();
- }
-
- // If the block size changed from the default, it is possible the block
- // that was loaded is the wrong block for the requested position.
- if (v.contains(file.key, requestedPosition)) {
- return v;
- }
- return getOrLoad(file, requestedPosition, ctx, fileChannel);
- }
-
- @SuppressWarnings("unchecked")
- private void reserveSpace(long reserve, DfsStreamKey key) {
- clockLock.lock();
- try {
- long live = LongStream.of(getCurrentSize()).sum() + reserve;
- if (maxBytes < live) {
- Ref prev = clockHand;
- Ref hand = clockHand.next;
- do {
- if (hand.isHot()) {
- // Value was recently touched. Cache is still hot so
- // give it another chance, but cool it down a bit.
- hand.markColder();
- prev = hand;
- hand = hand.next;
- continue;
- } else if (prev == hand)
- break;
-
- // No recent access since last scan, kill
- // value and remove from clock.
- Ref dead = hand;
- hand = hand.next;
- prev.next = hand;
- dead.next = null;
- dead.value = null;
- live -= dead.size;
- getStat(liveBytes, dead.key).addAndGet(-dead.size);
- getStat(statEvict, dead.key).incrementAndGet();
- reportIndexEvicted(dead);
- } while (maxBytes < live);
- clockHand = prev;
- }
- getStat(liveBytes, key).addAndGet(reserve);
- } finally {
- clockLock.unlock();
- }
- }
-
- private void creditSpace(long credit, DfsStreamKey key) {
- clockLock.lock();
- try {
- getStat(liveBytes, key).addAndGet(-credit);
- } finally {
- clockLock.unlock();
- }
- }
-
- @SuppressWarnings("unchecked")
- private void addToClock(Ref ref, long credit) {
- clockLock.lock();
- try {
- if (credit != 0) {
- getStat(liveBytes, ref.key).addAndGet(-credit);
- }
- Ref ptr = clockHand;
- ref.next = ptr.next;
- ptr.next = ref;
- clockHand = ref;
- } finally {
- clockLock.unlock();
- }
+ return dfsBlockCacheTable.getOrLoad(file, position, ctx, fileChannel);
}
void put(DfsBlock v) {
- put(v.stream, v.start, v.size(), v);
+ dfsBlockCacheTable.put(v);
}
/**
@@ -524,252 +256,46 @@ public final class DfsBlockCache {
* @throws IOException
* the reference was not in the cache and could not be loaded.
*/
- <T> Ref<T> getOrLoadRef(
- DfsStreamKey key, long position, RefLoader<T> loader)
- throws IOException {
- long start = System.nanoTime();
- int slot = slot(key, position);
- HashEntry e1 = table.get(slot);
- Ref<T> ref = scanRef(e1, key, position);
- if (ref != null) {
- getStat(statHit, key).incrementAndGet();
- reportIndexRequested(ref, true /* cacheHit */, start);
- return ref;
- }
-
- ReentrantLock regionLock = lockForRef(key);
- long lockStart = System.currentTimeMillis();
- regionLock.lock();
- try {
- HashEntry e2 = table.get(slot);
- if (e2 != e1) {
- ref = scanRef(e2, key, position);
- if (ref != null) {
- getStat(statHit, key).incrementAndGet();
- reportIndexRequested(ref, true /* cacheHit */,
- start);
- return ref;
- }
- }
-
- if (refLockWaitTime != null) {
- refLockWaitTime.accept(
- Long.valueOf(System.currentTimeMillis() - lockStart));
- }
- getStat(statMiss, key).incrementAndGet();
- ref = loader.load();
- ref.markHotter();
- // Reserve after loading to get the size of the object
- reserveSpace(ref.size, key);
- for (;;) {
- HashEntry n = new HashEntry(clean(e2), ref);
- if (table.compareAndSet(slot, e2, n)) {
- break;
- }
- e2 = table.get(slot);
- }
- addToClock(ref, 0);
- } finally {
- regionLock.unlock();
- }
- reportIndexRequested(ref, false /* cacheHit */, start);
- return ref;
+ <T> Ref<T> getOrLoadRef(DfsStreamKey key, long position,
+ RefLoader<T> loader) throws IOException {
+ return dfsBlockCacheTable.getOrLoadRef(key, position, loader);
}
<T> Ref<T> putRef(DfsStreamKey key, long size, T v) {
- return put(key, 0, size, v);
+ return dfsBlockCacheTable.putRef(key, size, v);
}
<T> Ref<T> put(DfsStreamKey key, long pos, long size, T v) {
- int slot = slot(key, pos);
- HashEntry e1 = table.get(slot);
- Ref<T> ref = scanRef(e1, key, pos);
- if (ref != null) {
- return ref;
- }
-
- reserveSpace(size, key);
- ReentrantLock regionLock = lockFor(key, pos);
- regionLock.lock();
- try {
- HashEntry e2 = table.get(slot);
- if (e2 != e1) {
- ref = scanRef(e2, key, pos);
- if (ref != null) {
- creditSpace(size, key);
- return ref;
- }
- }
-
- ref = new Ref<>(key, pos, size, v);
- ref.markHotter();
- for (;;) {
- HashEntry n = new HashEntry(clean(e2), ref);
- if (table.compareAndSet(slot, e2, n)) {
- break;
- }
- e2 = table.get(slot);
- }
- addToClock(ref, 0);
- } finally {
- regionLock.unlock();
- }
- return ref;
+ return dfsBlockCacheTable.put(key, pos, size, v);
}
boolean contains(DfsStreamKey key, long position) {
- return scan(table.get(slot(key, position)), key, position) != null;
+ return dfsBlockCacheTable.contains(key, position);
}
- @SuppressWarnings("unchecked")
<T> T get(DfsStreamKey key, long position) {
- T val = (T) scan(table.get(slot(key, position)), key, position);
- if (val == null) {
- getStat(statMiss, key).incrementAndGet();
- } else {
- getStat(statHit, key).incrementAndGet();
- }
- return val;
- }
-
- private <T> T scan(HashEntry n, DfsStreamKey key, long position) {
- Ref<T> r = scanRef(n, key, position);
- return r != null ? r.get() : null;
- }
-
- @SuppressWarnings("unchecked")
- private <T> Ref<T> scanRef(HashEntry n, DfsStreamKey key, long position) {
- for (; n != null; n = n.next) {
- Ref<T> r = n.ref;
- if (r.position == position && r.key.equals(key)) {
- return r.get() != null ? r : null;
- }
- }
- return null;
- }
-
- private int slot(DfsStreamKey key, long position) {
- return (hash(key.hash, position) >>> 1) % tableSize;
- }
-
- private ReentrantLock lockFor(DfsStreamKey key, long position) {
- return loadLocks[(hash(key.hash, position) >>> 1) % loadLocks.length];
- }
-
- private ReentrantLock lockForRef(DfsStreamKey key) {
- int slot = (key.hash >>> 1) % refLocks[key.packExtPos].length;
- return refLocks[key.packExtPos][slot];
- }
-
- private static AtomicLong[] newCounters() {
- AtomicLong[] ret = new AtomicLong[PackExt.values().length];
- for (int i = 0; i < ret.length; i++) {
- ret[i] = new AtomicLong();
- }
- return ret;
- }
-
- private static AtomicLong getStat(AtomicReference<AtomicLong[]> stats,
- DfsStreamKey key) {
- int pos = key.packExtPos;
- while (true) {
- AtomicLong[] vals = stats.get();
- if (pos < vals.length) {
- return vals[pos];
- }
- AtomicLong[] expect = vals;
- vals = new AtomicLong[Math.max(pos + 1, PackExt.values().length)];
- System.arraycopy(expect, 0, vals, 0, expect.length);
- for (int i = expect.length; i < vals.length; i++) {
- vals[i] = new AtomicLong();
- }
- if (stats.compareAndSet(expect, vals)) {
- return vals[pos];
- }
- }
+ return dfsBlockCacheTable.get(key, position);
}
- private static long[] getStatVals(AtomicReference<AtomicLong[]> stat) {
- AtomicLong[] stats = stat.get();
- long[] cnt = new long[stats.length];
- for (int i = 0; i < stats.length; i++) {
- cnt[i] = stats[i].get();
- }
- return cnt;
- }
-
- private static HashEntry clean(HashEntry top) {
- while (top != null && top.ref.next == null) {
- top = top.next;
- }
- if (top == null) {
- return null;
- }
- HashEntry n = clean(top.next);
- return n == top.next ? top : new HashEntry(n, top.ref);
- }
-
- private void reportIndexRequested(Ref<?> ref, boolean cacheHit,
- long start) {
- if (indexEventConsumer == null
- || !isIndexExtPos(ref.key.packExtPos)) {
- return;
- }
- EvictKey evictKey = new EvictKey(ref);
- Long prevEvictedTime = indexEvictionMap.get(evictKey);
- long now = System.nanoTime();
- long sinceLastEvictionNanos = prevEvictedTime == null ? 0L
- : now - prevEvictedTime.longValue();
- indexEventConsumer.acceptRequestedEvent(ref.key.packExtPos, cacheHit,
- (now - start) / 1000L /* micros */, ref.size,
- Duration.ofNanos(sinceLastEvictionNanos));
- }
-
- private void reportIndexEvicted(Ref<?> dead) {
- if (indexEventConsumer == null
- || !indexEventConsumer.shouldReportEvictedEvent()
- || !isIndexExtPos(dead.key.packExtPos)) {
- return;
- }
- EvictKey evictKey = new EvictKey(dead);
- Long prevEvictedTime = indexEvictionMap.get(evictKey);
- long now = System.nanoTime();
- long sinceLastEvictionNanos = prevEvictedTime == null ? 0L
- : now - prevEvictedTime.longValue();
- indexEvictionMap.put(evictKey, Long.valueOf(now));
- indexEventConsumer.acceptEvictedEvent(dead.key.packExtPos, dead.size,
- dead.totalHitCount.get(),
- Duration.ofNanos(sinceLastEvictionNanos));
- }
-
- private static boolean isIndexExtPos(int packExtPos) {
- return packExtPos == PackExt.INDEX.getPosition()
- || packExtPos == PackExt.REVERSE_INDEX.getPosition()
- || packExtPos == PackExt.BITMAP_INDEX.getPosition();
- }
-
- private static final class HashEntry {
- /** Next entry in the hash table's chain list. */
- final HashEntry next;
-
- /** The referenced object. */
- final Ref ref;
-
- HashEntry(HashEntry n, Ref r) {
- next = n;
- ref = r;
- }
+ private BlockCacheStats getAggregatedBlockCacheStats() {
+ return AggregatedBlockCacheStats
+ .fromStatsList(dfsBlockCacheTable.getBlockCacheStats());
}
static final class Ref<T> {
final DfsStreamKey key;
+
final long position;
+
final long size;
+
volatile T value;
+
Ref next;
private volatile int hotCount;
- private AtomicInteger totalHitCount = new AtomicInteger();
+
+ private final AtomicInteger totalHitCount = new AtomicInteger();
Ref(DfsStreamKey key, long position, long size, T v) {
this.key = key;
@@ -804,33 +330,9 @@ public final class DfsBlockCache {
boolean isHot() {
return hotCount > 0;
}
- }
-
- private static final class EvictKey {
- private final int keyHash;
- private final int packExtPos;
- private final long position;
-
- EvictKey(Ref<?> ref) {
- keyHash = ref.key.hash;
- packExtPos = ref.key.packExtPos;
- position = ref.position;
- }
-
- @Override
- public boolean equals(Object object) {
- if (object instanceof EvictKey) {
- EvictKey other = (EvictKey) object;
- return keyHash == other.keyHash
- && packExtPos == other.packExtPos
- && position == other.position;
- }
- return false;
- }
- @Override
- public int hashCode() {
- return DfsBlockCache.getInstance().hash(keyHash, position);
+ int getTotalHitCount() {
+ return totalHitCount.get();
}
}
@@ -845,8 +347,11 @@ public final class DfsBlockCache {
@FunctionalInterface
interface ReadableChannelSupplier {
/**
+ * Get ReadableChannel
+ *
* @return ReadableChannel
* @throws IOException
+ * if an IO error occurred
*/
ReadableChannel get() throws IOException;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
index 69a37058bf..17bf51876d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java
@@ -11,17 +11,27 @@
package org.eclipse.jgit.internal.storage.dfs;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DFS_CACHE_PREFIX;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DFS_SECTION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BLOCK_LIMIT;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BLOCK_SIZE;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_CONCURRENCY_LEVEL;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PACK_EXTENSIONS;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_RATIO;
+import java.io.PrintWriter;
import java.text.MessageFormat;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.pack.PackExt;
@@ -41,25 +51,103 @@ public class DfsBlockCacheConfig {
/** Default number of max cache hits. */
public static final int DEFAULT_CACHE_HOT_MAX = 1;
+ static final String DEFAULT_NAME = "<default>"; //$NON-NLS-1$
+
+ private String name;
+
private long blockLimit;
+
private int blockSize;
+
private double streamRatio;
+
private int concurrencyLevel;
private Consumer<Long> refLock;
+
private Map<PackExt, Integer> cacheHotMap;
private IndexEventConsumer indexEventConsumer;
+ private List<DfsBlockCachePackExtConfig> packExtCacheConfigurations;
+
/**
* Create a default configuration.
*/
public DfsBlockCacheConfig() {
+ name = DEFAULT_NAME;
setBlockLimit(32 * MB);
setBlockSize(64 * KB);
setStreamRatio(0.30);
setConcurrencyLevel(32);
cacheHotMap = Collections.emptyMap();
+ packExtCacheConfigurations = Collections.emptyList();
+ }
+
+ /**
+ * Print the current cache configuration to the given {@link PrintWriter}.
+ *
+ * @param writer
+ * {@link PrintWriter} to write the cache's configuration to.
+ */
+ public void print(PrintWriter writer) {
+ print(/* linePrefix= */ "", /* pad= */ " ", writer); //$NON-NLS-1$//$NON-NLS-2$
+ }
+
+ /**
+ * Print the current cache configuration to the given {@link PrintWriter}.
+ *
+ * @param linePrefix
+ * prefix to prepend all writen lines with. Ex a string of 0 or
+ * more " " entries.
+ * @param pad
+ * filler used to extend linePrefix. Ex a multiple of " ".
+ * @param writer
+ * {@link PrintWriter} to write the cache's configuration to.
+ */
+ @SuppressWarnings("nls")
+ private void print(String linePrefix, String pad, PrintWriter writer) {
+ String currentPrefixLevel = linePrefix;
+ if (!name.isEmpty() || !packExtCacheConfigurations.isEmpty()) {
+ writer.println(linePrefix + "Name: "
+ + (name.isEmpty() ? DEFAULT_NAME : this.name));
+ currentPrefixLevel += pad;
+ }
+ writer.println(currentPrefixLevel + "BlockLimit: " + blockLimit);
+ writer.println(currentPrefixLevel + "BlockSize: " + blockSize);
+ writer.println(currentPrefixLevel + "StreamRatio: " + streamRatio);
+ writer.println(
+ currentPrefixLevel + "ConcurrencyLevel: " + concurrencyLevel);
+ for (Map.Entry<PackExt, Integer> entry : cacheHotMap.entrySet()) {
+ writer.println(currentPrefixLevel + "CacheHotMapEntry: "
+ + entry.getKey() + " : " + entry.getValue());
+ }
+ for (DfsBlockCachePackExtConfig extConfig : packExtCacheConfigurations) {
+ extConfig.print(currentPrefixLevel, pad, writer);
+ }
+ }
+
+ /**
+ * Get the name for the block cache configured by this cache config.
+ *
+ * @return the name for the block cache configured by this cache config.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Set the name for the block cache configured by this cache config.
+ * <p>
+ * Made visible for testing.
+ *
+ * @param name
+ * the name for the block cache configured by this cache config.
+ * @return {@code this}
+ */
+ DfsBlockCacheConfig setName(String name) {
+ this.name = name;
+ return this;
}
/**
@@ -77,10 +165,10 @@ public class DfsBlockCacheConfig {
* Set maximum number bytes of heap memory to dedicate to caching pack file
* data.
* <p>
- * It is strongly recommended to set the block limit to be an integer multiple
- * of the block size. This constraint is not enforced by this method (since
- * it may be called before {@link #setBlockSize(int)}), but it is enforced by
- * {@link #fromConfig(Config)}.
+ * It is strongly recommended to set the block limit to be an integer
+ * multiple of the block size. This constraint is not enforced by this
+ * method (since it may be called before {@link #setBlockSize(int)}), but it
+ * is enforced by {@link #fromConfig(Config)}.
*
* @param newLimit
* maximum number bytes of heap memory to dedicate to caching
@@ -89,9 +177,9 @@ public class DfsBlockCacheConfig {
*/
public DfsBlockCacheConfig setBlockLimit(long newLimit) {
if (newLimit <= 0) {
- throw new IllegalArgumentException(MessageFormat.format(
- JGitText.get().blockLimitNotPositive,
- Long.valueOf(newLimit)));
+ throw new IllegalArgumentException(
+ MessageFormat.format(JGitText.get().blockLimitNotPositive,
+ Long.valueOf(newLimit)));
}
blockLimit = newLimit;
return this;
@@ -211,12 +299,24 @@ public class DfsBlockCacheConfig {
* map of hot count per pack extension for {@code DfsBlockCache}.
* @return {@code this}
*/
+ /*
+ * TODO The cache HotMap configuration should be set as a config option and
+ * not passed in through a setter.
+ */
public DfsBlockCacheConfig setCacheHotMap(
Map<PackExt, Integer> cacheHotMap) {
this.cacheHotMap = Collections.unmodifiableMap(cacheHotMap);
+ setCacheHotMapToPackExtConfigs(this.cacheHotMap);
return this;
}
+ private void setCacheHotMapToPackExtConfigs(
+ Map<PackExt, Integer> cacheHotMap) {
+ for (DfsBlockCachePackExtConfig packExtConfig : packExtCacheConfigurations) {
+ packExtConfig.setCacheHotMap(cacheHotMap);
+ }
+ }
+
/**
* Get the consumer of cache index events.
*
@@ -240,61 +340,121 @@ public class DfsBlockCacheConfig {
}
/**
+ * Get the list of pack ext cache configs.
+ *
+ * @return the list of pack ext cache configs.
+ */
+ List<DfsBlockCachePackExtConfig> getPackExtCacheConfigurations() {
+ return packExtCacheConfigurations;
+ }
+
+ /**
+ * Set the list of pack ext cache configs.
+ *
+ * Made visible for testing.
+ *
+ * @param packExtCacheConfigurations
+ * the list of pack ext cache configs to set.
+ * @return {@code this}
+ */
+ DfsBlockCacheConfig setPackExtCacheConfigurations(
+ List<DfsBlockCachePackExtConfig> packExtCacheConfigurations) {
+ this.packExtCacheConfigurations = packExtCacheConfigurations;
+ return this;
+ }
+
+ /**
* Update properties by setting fields from the configuration.
* <p>
* If a property is not defined in the configuration, then it is left
* unmodified.
* <p>
- * Enforces certain constraints on the combination of settings in the config,
- * for example that the block limit is a multiple of the block size.
+ * Enforces certain constraints on the combination of settings in the
+ * config, for example that the block limit is a multiple of the block size.
*
* @param rc
* configuration to read properties from.
* @return {@code this}
*/
public DfsBlockCacheConfig fromConfig(Config rc) {
- long cfgBlockLimit = rc.getLong(
- CONFIG_CORE_SECTION,
- CONFIG_DFS_SECTION,
- CONFIG_KEY_BLOCK_LIMIT,
- getBlockLimit());
- int cfgBlockSize = rc.getInt(
- CONFIG_CORE_SECTION,
- CONFIG_DFS_SECTION,
- CONFIG_KEY_BLOCK_SIZE,
+ fromConfig(CONFIG_CORE_SECTION, CONFIG_DFS_SECTION, rc);
+ loadPackExtConfigs(rc);
+ return this;
+ }
+
+ private void fromConfig(String section, String subSection, Config rc) {
+ long cfgBlockLimit = rc.getLong(section, subSection,
+ CONFIG_KEY_BLOCK_LIMIT, getBlockLimit());
+ int cfgBlockSize = rc.getInt(section, subSection, CONFIG_KEY_BLOCK_SIZE,
getBlockSize());
if (cfgBlockLimit % cfgBlockSize != 0) {
throw new IllegalArgumentException(MessageFormat.format(
JGitText.get().blockLimitNotMultipleOfBlockSize,
- Long.valueOf(cfgBlockLimit),
- Long.valueOf(cfgBlockSize)));
+ Long.valueOf(cfgBlockLimit), Long.valueOf(cfgBlockSize)));
}
+ // Set name only if `core dfs` is configured, otherwise fall back to the
+ // default.
+ if (rc.getSubsections(section).contains(subSection)) {
+ this.name = subSection;
+ }
setBlockLimit(cfgBlockLimit);
setBlockSize(cfgBlockSize);
- setConcurrencyLevel(rc.getInt(
- CONFIG_CORE_SECTION,
- CONFIG_DFS_SECTION,
- CONFIG_KEY_CONCURRENCY_LEVEL,
- getConcurrencyLevel()));
+ setConcurrencyLevel(rc.getInt(section, subSection,
+ CONFIG_KEY_CONCURRENCY_LEVEL, getConcurrencyLevel()));
- String v = rc.getString(
- CONFIG_CORE_SECTION,
- CONFIG_DFS_SECTION,
- CONFIG_KEY_STREAM_RATIO);
+ String v = rc.getString(section, subSection, CONFIG_KEY_STREAM_RATIO);
if (v != null) {
try {
setStreamRatio(Double.parseDouble(v));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(MessageFormat.format(
- JGitText.get().enumValueNotSupported3,
- CONFIG_CORE_SECTION,
- CONFIG_DFS_SECTION,
- CONFIG_KEY_STREAM_RATIO, v), e);
+ JGitText.get().enumValueNotSupported3, section,
+ subSection, CONFIG_KEY_STREAM_RATIO, v), e);
}
}
- return this;
+ }
+
+ private void loadPackExtConfigs(Config config) {
+ List<String> subSections = config.getSubsections(CONFIG_CORE_SECTION)
+ .stream()
+ .filter(section -> section.startsWith(CONFIG_DFS_CACHE_PREFIX))
+ .collect(Collectors.toList());
+ if (subSections.size() == 0) {
+ return;
+ }
+ ArrayList<DfsBlockCachePackExtConfig> cacheConfigs = new ArrayList<>();
+ Set<PackExt> extensionsSeen = new HashSet<>();
+ for (String subSection : subSections) {
+ var cacheConfig = DfsBlockCachePackExtConfig.fromConfig(config,
+ CONFIG_CORE_SECTION, subSection);
+ Set<PackExt> packExtsDuplicates = intersection(extensionsSeen,
+ cacheConfig.packExts);
+ if (packExtsDuplicates.size() > 0) {
+ String duplicatePackExts = packExtsDuplicates.stream()
+ .map(PackExt::toString)
+ .collect(Collectors.joining(",")); //$NON-NLS-1$
+ throw new IllegalArgumentException(MessageFormat.format(
+ JGitText.get().duplicatePackExtensionsSet,
+ CONFIG_CORE_SECTION, subSection,
+ CONFIG_KEY_PACK_EXTENSIONS, duplicatePackExts));
+ }
+ extensionsSeen.addAll(cacheConfig.packExts);
+ cacheConfigs.add(cacheConfig);
+ }
+ packExtCacheConfigurations = cacheConfigs;
+ setCacheHotMapToPackExtConfigs(this.cacheHotMap);
+ }
+
+ private static <T> Set<T> intersection(Set<T> first, Set<T> second) {
+ Set<T> ret = new HashSet<>();
+ for (T entry : second) {
+ if (first.contains(entry)) {
+ ret.add(entry);
+ }
+ }
+ return ret;
}
/** Consumer of DfsBlockCache loading and eviction events for indexes. */
@@ -338,10 +498,110 @@ public class DfsBlockCacheConfig {
}
/**
+ * Whether evicted events should be reported
+ *
* @return true if reporting evicted events is enabled.
*/
default boolean shouldReportEvictedEvent() {
return false;
}
}
-} \ No newline at end of file
+
+ /**
+ * A configuration for a single cache table storing 1 or more Pack
+ * extensions.
+ * <p>
+ * The current pack ext cache tables implementation supports the same
+ * parameters the ClockBlockCacheTable (current default implementation).
+ * <p>
+ * Configuration falls back to the defaults coded values defined in the
+ * {@link DfsBlockCacheConfig} when not set on each cache table
+ * configuration and NOT the values of the basic dfs section.
+ * <p>
+ * <code>
+ *
+ * Format:
+ * [core "dfs.packCache"]
+ * packExtensions = "PACK"
+ * blockSize = 512
+ * blockLimit = 100
+ * concurrencyLevel = 5
+ *
+ * [core "dfs.multipleExtensionCache"]
+ * packExtensions = "INDEX REFTABLE BITMAP_INDEX"
+ * blockSize = 512
+ * blockLimit = 100
+ * concurrencyLevel = 5
+ * </code>
+ */
+ static class DfsBlockCachePackExtConfig {
+ // Set of pack extensions that will map to the cache instance.
+ private final EnumSet<PackExt> packExts;
+
+ // Configuration for the cache instance.
+ private final DfsBlockCacheConfig packExtCacheConfiguration;
+
+ /**
+ * Made visible for testing.
+ *
+ * @param packExts
+ * Set of {@link PackExt}s associated to this cache config.
+ * @param packExtCacheConfiguration
+ * {@link DfsBlockCacheConfig} for this cache config.
+ */
+ DfsBlockCachePackExtConfig(EnumSet<PackExt> packExts,
+ DfsBlockCacheConfig packExtCacheConfiguration) {
+ this.packExts = packExts;
+ this.packExtCacheConfiguration = packExtCacheConfiguration;
+ }
+
+ Set<PackExt> getPackExts() {
+ return packExts;
+ }
+
+ DfsBlockCacheConfig getPackExtCacheConfiguration() {
+ return packExtCacheConfiguration;
+ }
+
+ void setCacheHotMap(Map<PackExt, Integer> cacheHotMap) {
+ Map<PackExt, Integer> packExtHotMap = packExts.stream()
+ .filter(cacheHotMap::containsKey)
+ .collect(Collectors.toUnmodifiableMap(Function.identity(),
+ cacheHotMap::get));
+ packExtCacheConfiguration.setCacheHotMap(packExtHotMap);
+ }
+
+ private static DfsBlockCachePackExtConfig fromConfig(Config config,
+ String section, String subSection) {
+ String packExtensions = config.getString(section, subSection,
+ CONFIG_KEY_PACK_EXTENSIONS);
+ if (packExtensions == null) {
+ throw new IllegalArgumentException(
+ JGitText.get().noPackExtGivenForConfiguration);
+ }
+ String[] extensions = packExtensions.split(" ", -1); //$NON-NLS-1$
+ Set<PackExt> packExts = new HashSet<>(extensions.length);
+ for (String extension : extensions) {
+ try {
+ packExts.add(PackExt.valueOf(extension));
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException(MessageFormat.format(
+ JGitText.get().unknownPackExtension, section,
+ subSection, CONFIG_KEY_PACK_EXTENSIONS, extension),
+ e);
+ }
+ }
+
+ DfsBlockCacheConfig dfsBlockCacheConfig = new DfsBlockCacheConfig();
+ dfsBlockCacheConfig.fromConfig(section, subSection, config);
+ return new DfsBlockCachePackExtConfig(EnumSet.copyOf(packExts),
+ dfsBlockCacheConfig);
+ }
+
+ void print(String linePrefix, String pad, PrintWriter writer) {
+ packExtCacheConfiguration.print(linePrefix, pad, writer);
+ writer.println(linePrefix + pad + "PackExts: " //$NON-NLS-1$
+ + packExts.stream().sorted().collect(Collectors.toList()));
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheStats.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheStats.java
new file mode 100644
index 0000000000..436f57437e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheStats.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2024, Google LLC and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import static org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheTable.BlockCacheStats;
+
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+
+/**
+ * Keeps track of stats for a Block Cache table.
+ */
+class DfsBlockCacheStats implements BlockCacheStats {
+ private final String name;
+
+ /**
+ * Number of times a block was found in the cache, per pack file extension.
+ */
+ private final AtomicReference<AtomicLong[]> statHit;
+
+ /**
+ * Number of times a block was not found, and had to be loaded, per pack
+ * file extension.
+ */
+ private final AtomicReference<AtomicLong[]> statMiss;
+
+ /**
+ * Number of blocks evicted due to cache being full, per pack file
+ * extension.
+ */
+ private final AtomicReference<AtomicLong[]> statEvict;
+
+ /**
+ * Number of bytes currently loaded in the cache, per pack file extension.
+ */
+ private final AtomicReference<AtomicLong[]> liveBytes;
+
+ DfsBlockCacheStats() {
+ this(""); //$NON-NLS-1$
+ }
+
+ DfsBlockCacheStats(String name) {
+ this.name = name;
+ statHit = new AtomicReference<>(newCounters());
+ statMiss = new AtomicReference<>(newCounters());
+ statEvict = new AtomicReference<>(newCounters());
+ liveBytes = new AtomicReference<>(newCounters());
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Increment the {@code statHit} count.
+ *
+ * @param key
+ * key identifying which liveBytes entry to update.
+ */
+ void incrementHit(DfsStreamKey key) {
+ getStat(statHit, key).incrementAndGet();
+ }
+
+ /**
+ * Increment the {@code statMiss} count.
+ *
+ * @param key
+ * key identifying which liveBytes entry to update.
+ */
+ void incrementMiss(DfsStreamKey key) {
+ getStat(statMiss, key).incrementAndGet();
+ }
+
+ /**
+ * Increment the {@code statEvict} count.
+ *
+ * @param key
+ * key identifying which liveBytes entry to update.
+ */
+ void incrementEvict(DfsStreamKey key) {
+ getStat(statEvict, key).incrementAndGet();
+ }
+
+ /**
+ * Add {@code size} to the {@code liveBytes} count.
+ *
+ * @param key
+ * key identifying which liveBytes entry to update.
+ * @param size
+ * amount to increment the count by.
+ */
+ void addToLiveBytes(DfsStreamKey key, long size) {
+ getStat(liveBytes, key).addAndGet(size);
+ }
+
+ @Override
+ public long[] getCurrentSize() {
+ return getStatVals(liveBytes);
+ }
+
+ @Override
+ public long[] getHitCount() {
+ return getStatVals(statHit);
+ }
+
+ @Override
+ public long[] getMissCount() {
+ return getStatVals(statMiss);
+ }
+
+ @Override
+ public long[] getTotalRequestCount() {
+ AtomicLong[] hit = statHit.get();
+ AtomicLong[] miss = statMiss.get();
+ long[] cnt = new long[Math.max(hit.length, miss.length)];
+ for (int i = 0; i < hit.length; i++) {
+ cnt[i] += hit[i].get();
+ }
+ for (int i = 0; i < miss.length; i++) {
+ cnt[i] += miss[i].get();
+ }
+ return cnt;
+ }
+
+ @Override
+ public long[] getHitRatio() {
+ AtomicLong[] hit = statHit.get();
+ AtomicLong[] miss = statMiss.get();
+ long[] ratio = new long[Math.max(hit.length, miss.length)];
+ for (int i = 0; i < ratio.length; i++) {
+ if (i >= hit.length) {
+ ratio[i] = 0;
+ } else if (i >= miss.length) {
+ ratio[i] = 100;
+ } else {
+ long hitVal = hit[i].get();
+ long missVal = miss[i].get();
+ long total = hitVal + missVal;
+ ratio[i] = total == 0 ? 0 : hitVal * 100 / total;
+ }
+ }
+ return ratio;
+ }
+
+ @Override
+ public long[] getEvictions() {
+ return getStatVals(statEvict);
+ }
+
+ private static AtomicLong[] newCounters() {
+ AtomicLong[] ret = new AtomicLong[PackExt.values().length];
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = new AtomicLong();
+ }
+ return ret;
+ }
+
+ private static long[] getStatVals(AtomicReference<AtomicLong[]> stat) {
+ AtomicLong[] stats = stat.get();
+ long[] cnt = new long[stats.length];
+ for (int i = 0; i < stats.length; i++) {
+ cnt[i] = stats[i].get();
+ }
+ return cnt;
+ }
+
+ private static AtomicLong getStat(AtomicReference<AtomicLong[]> stats,
+ DfsStreamKey key) {
+ int pos = key.packExtPos;
+ while (true) {
+ AtomicLong[] vals = stats.get();
+ if (pos < vals.length) {
+ return vals[pos];
+ }
+ AtomicLong[] expect = vals;
+ vals = new AtomicLong[Math.max(pos + 1, PackExt.values().length)];
+ System.arraycopy(expect, 0, vals, 0, expect.length);
+ for (int i = expect.length; i < vals.length; i++) {
+ vals[i] = new AtomicLong();
+ }
+ if (stats.compareAndSet(expect, vals)) {
+ return vals[pos];
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTable.java
new file mode 100644
index 0000000000..c3fd07b7bb
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheTable.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2024, Google LLC and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Block cache table.
+ */
+public interface DfsBlockCacheTable {
+ /**
+ * Quickly check if the cache contains block 0 of the given stream.
+ * <p>
+ * This can be useful for sophisticated pre-read algorithms to quickly
+ * determine if a file is likely already in cache, especially small
+ * reftables which may be smaller than a typical DFS block size.
+ *
+ * @param key
+ * the file to check.
+ * @return true if block 0 (the first block) is in the cache.
+ */
+ boolean hasBlock0(DfsStreamKey key);
+
+ /**
+ * Look up a cached object, creating and loading it if it doesn't exist.
+ *
+ * @param file
+ * the pack that "contains" the cached object.
+ * @param position
+ * offset within <code>pack</code> of the object.
+ * @param dfsReader
+ * current thread's reader.
+ * @param fileChannel
+ * supplier for channel to read {@code pack}.
+ * @return the object reference.
+ * @throws IOException
+ * the reference was not in the cache and could not be loaded.
+ */
+ DfsBlock getOrLoad(BlockBasedFile file, long position, DfsReader dfsReader,
+ DfsBlockCache.ReadableChannelSupplier fileChannel)
+ throws IOException;
+
+ /**
+ * Look up a cached object, creating and loading it if it doesn't exist.
+ *
+ * @param key
+ * the stream key of the pack.
+ * @param position
+ * the position in the key. The default should be 0.
+ * @param loader
+ * the function to load the reference.
+ * @return the object reference.
+ * @throws IOException
+ * the reference was not in the cache and could not be loaded.
+ */
+ <T> DfsBlockCache.Ref<T> getOrLoadRef(DfsStreamKey key, long position,
+ DfsBlockCache.RefLoader<T> loader) throws IOException;
+
+ /**
+ * Put a block in the block cache.
+ *
+ * @param v
+ * the block to put in the cache.
+ */
+ void put(DfsBlock v);
+
+ /**
+ * Put a block in the block cache.
+ *
+ * @param key
+ * the stream key of the pack.
+ * @param pos
+ * the position in the key.
+ * @param size
+ * the size of the object.
+ * @param v
+ * the object to put in the block cache.
+ * @return the object reference.
+ */
+ <T> DfsBlockCache.Ref<T> put(DfsStreamKey key, long pos, long size, T v);
+
+ /**
+ * Put an object in the block cache.
+ *
+ * @param key
+ * the stream key of the pack.
+ * @param size
+ * the size of the object.
+ * @param v
+ * the object to put in the block cache.
+ * @return the object reference.
+ */
+ <T> DfsBlockCache.Ref<T> putRef(DfsStreamKey key, long size, T v);
+
+ /**
+ * Check if the block cache contains an object identified by (key,
+ * position).
+ *
+ * @param key
+ * the stream key of the pack.
+ * @param position
+ * the position in the key.
+ * @return if the block cache contains the object identified by (key,
+ * position).
+ */
+ boolean contains(DfsStreamKey key, long position);
+
+ /**
+ * Get the object identified by (key, position) from the block cache.
+ *
+ * @param key
+ * the stream key of the pack.
+ * @param position
+ * the position in the key.
+ * @return the object identified by (key, position).
+ */
+ <T> T get(DfsStreamKey key, long position);
+
+ /**
+ * Get the list of {@link BlockCacheStats} held by this cache.
+ * <p>
+ * The returned list has a {@link BlockCacheStats} per configured cache
+ * table, with a minimum of 1 {@link BlockCacheStats} object returned.
+ *
+ * Use {@link AggregatedBlockCacheStats} to combine the results of the stats
+ * in the list for an aggregated view of the cache's stats.
+ *
+ * @return the list of {@link BlockCacheStats} held by this cache.
+ */
+ List<BlockCacheStats> getBlockCacheStats();
+
+ /**
+ * Get the name of the table.
+ *
+ * @return this table's name.
+ */
+ String getName();
+
+ /**
+ * Provides methods used with Block Cache statistics.
+ */
+ interface BlockCacheStats {
+
+ /**
+ * Get the name of the block cache generating this instance.
+ *
+ * @return this cache's name.
+ */
+ String getName();
+
+ /**
+ * Get total number of bytes in the cache, per pack file extension.
+ *
+ * @return total number of bytes in the cache, per pack file extension.
+ */
+ long[] getCurrentSize();
+
+ /**
+ * Get number of requests for items in the cache, per pack file
+ * extension.
+ *
+ * @return the number of requests for items in the cache, per pack file
+ * extension.
+ */
+ long[] getHitCount();
+
+ /**
+ * Get number of requests for items not in the cache, per pack file
+ * extension.
+ *
+ * @return the number of requests for items not in the cache, per pack
+ * file extension.
+ */
+ long[] getMissCount();
+
+ /**
+ * Get total number of requests (hit + miss), per pack file extension.
+ *
+ * @return total number of requests (hit + miss), per pack file
+ * extension.
+ */
+ long[] getTotalRequestCount();
+
+ /**
+ * Get hit ratios.
+ *
+ * @return hit ratios.
+ */
+ long[] getHitRatio();
+
+ /**
+ * Get number of evictions performed due to cache being full, per pack
+ * file extension.
+ *
+ * @return the number of evictions performed due to cache being full,
+ * per pack file extension.
+ */
+ long[] getEvictions();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java
index 22959e9f6b..af8cd46d01 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java
@@ -28,6 +28,8 @@ public class DfsCachedPack extends CachedPack {
}
/**
+ * Get pack file
+ *
* @return the pack passed to the constructor
*/
public DfsPackFile getPackFile() {
@@ -43,19 +45,16 @@ public class DfsCachedPack extends CachedPack {
return pack.getPackDescription();
}
- /** {@inheritDoc} */
@Override
public long getObjectCount() throws IOException {
return getPackDescription().getObjectCount();
}
- /** {@inheritDoc} */
@Override
public long getDeltaCount() throws IOException {
return getPackDescription().getDeltaCount();
}
- /** {@inheritDoc} */
@Override
public boolean hasObject(ObjectToPack obj, StoredObjectRepresentation rep) {
return ((DfsObjectRepresentation) rep).pack == pack;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsConfig.java
index 2ee23f8627..4f0e4a55d4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsConfig.java
@@ -22,13 +22,11 @@ import org.eclipse.jgit.lib.StoredConfig;
* simply clears the config, and saving does nothing.
*/
public final class DfsConfig extends StoredConfig {
- /** {@inheritDoc} */
@Override
public void load() throws IOException, ConfigInvalidException {
clear();
}
- /** {@inheritDoc} */
@Override
public void save() throws IOException {
// TODO actually store this configuration.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
index 66bcf73987..199481cf33 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java
@@ -17,14 +17,14 @@ import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.IN
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.RECEIVE;
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
import static org.eclipse.jgit.internal.storage.dfs.DfsPackCompactor.configureReftable;
-import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.COMMIT_GRAPH;
-import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.OBJECT_SIZE_INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
import static org.eclipse.jgit.internal.storage.pack.PackWriter.NONE;
import java.io.IOException;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -35,7 +35,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.commitgraph.CommitGraphWriter;
@@ -80,6 +79,8 @@ public class DfsGarbageCollector {
private ReftableConfig reftableConfig;
private boolean convertToReftable = true;
private boolean writeCommitGraph;
+
+ private boolean writeBloomFilter;
private boolean includeDeletes;
private long reftableInitialMinUpdateIndex = 1;
private long reftableInitialMaxUpdateIndex = 1;
@@ -89,7 +90,7 @@ public class DfsGarbageCollector {
private long coalesceGarbageLimit = 50 << 20;
private long garbageTtlMillis = TimeUnit.DAYS.toMillis(1);
- private long startTimeMillis;
+ private Instant startTime;
private List<DfsPackFile> packsBefore;
private List<DfsReftable> reftablesBefore;
private List<DfsPackFile> expiredGarbagePacks;
@@ -99,6 +100,7 @@ public class DfsGarbageCollector {
private Set<ObjectId> allTags;
private Set<ObjectId> nonHeads;
private Set<ObjectId> tagTargets;
+ private Instant refLogExpire;
/**
* Initialize a garbage collector.
@@ -199,6 +201,22 @@ public class DfsGarbageCollector {
return this;
}
+
+ /**
+ * Set time limit to the reflog history.
+ * <p>
+ * Garbage Collector prunes entries from reflog history older than {@code refLogExpire}
+ * <p>
+ *
+ * @param refLogExpire
+ * instant in time which defines refLog expiration
+ * @return {@code this}
+ */
+ public DfsGarbageCollector setRefLogExpire(Instant refLogExpire) {
+ this.refLogExpire = refLogExpire;
+ return this;
+ }
+
/**
* Set maxUpdateIndex for the initial reftable created during conversion.
*
@@ -298,6 +316,20 @@ public class DfsGarbageCollector {
}
/**
+ * Toggle bloom filter generation.
+ * <p>
+ * False by default.
+ *
+ * @param enable
+ * Whether bloom filter generation is enabled
+ * @return {@code this}
+ */
+ public DfsGarbageCollector setWriteBloomFilter(boolean enable) {
+ writeBloomFilter = enable;
+ return this;
+ }
+
+ /**
* Create a single new pack file containing all of the live objects.
* <p>
* This method safely decides which packs can be expired after the new pack
@@ -320,7 +352,7 @@ public class DfsGarbageCollector {
throw new IllegalStateException(
JGitText.get().supportOnlyPackIndexVersion2);
- startTimeMillis = SystemReader.getInstance().getCurrentTime();
+ startTime = SystemReader.getInstance().now();
ctx = objdb.newReader();
try {
refdb.refresh();
@@ -403,7 +435,7 @@ public class DfsGarbageCollector {
packsBefore = new ArrayList<>(packs.length);
expiredGarbagePacks = new ArrayList<>(packs.length);
- long now = SystemReader.getInstance().getCurrentTime();
+ long now = SystemReader.getInstance().now().toEpochMilli();
for (DfsPackFile p : packs) {
DfsPackDescription d = p.getPackDescription();
if (d.getPackSource() != UNREACHABLE_GARBAGE) {
@@ -577,6 +609,7 @@ public class DfsGarbageCollector {
cfg.setReuseObjects(true);
cfg.setDeltaCompress(false);
cfg.setBuildBitmaps(false);
+ cfg.setWriteReverseIndex(false);
try (PackWriter pw = new PackWriter(cfg, ctx);
RevWalk pool = new RevWalk(ctx)) {
@@ -671,28 +704,26 @@ public class DfsGarbageCollector {
pack.setBlockSize(PACK, out.blockSize());
}
- try (DfsOutputStream out = objdb.writeFile(pack, INDEX)) {
- CountingOutputStream cnt = new CountingOutputStream(out);
- pw.writeIndex(cnt);
- pack.addFileExt(INDEX);
- pack.setFileSize(INDEX, cnt.getCount());
- pack.setBlockSize(INDEX, out.blockSize());
- pack.setIndexVersion(pw.getIndexVersion());
- }
+ pw.writeIndex(objdb.getPackIndexWriter(pack, pw.getIndexVersion()));
- if (pw.prepareBitmapIndex(pm)) {
- try (DfsOutputStream out = objdb.writeFile(pack, BITMAP_INDEX)) {
+ if (source != UNREACHABLE_GARBAGE && packConfig.getMinBytesForObjSizeIndex() >= 0) {
+ try (DfsOutputStream out = objdb.writeFile(pack,
+ OBJECT_SIZE_INDEX)) {
CountingOutputStream cnt = new CountingOutputStream(out);
- pw.writeBitmapIndex(cnt);
- pack.addFileExt(BITMAP_INDEX);
- pack.setFileSize(BITMAP_INDEX, cnt.getCount());
- pack.setBlockSize(BITMAP_INDEX, out.blockSize());
+ pw.writeObjectSizeIndex(cnt);
+ pack.addFileExt(OBJECT_SIZE_INDEX);
+ pack.setFileSize(OBJECT_SIZE_INDEX, cnt.getCount());
+ pack.setBlockSize(OBJECT_SIZE_INDEX, out.blockSize());
}
}
+ if (pw.prepareBitmapIndex(pm)) {
+ pw.writeBitmapIndex(objdb.getPackBitmapIndexWriter(pack));
+ }
+
PackStatistics stats = pw.getStatistics();
pack.setPackStats(stats);
- pack.setLastModified(startTimeMillis);
+ pack.setLastModified(startTime.toEpochMilli());
newPackDesc.add(pack);
newPackStats.add(stats);
newPackObj.add(pw.getObjectSet());
@@ -720,6 +751,10 @@ public class DfsGarbageCollector {
compact.addAll(stack.readers());
compact.setIncludeDeletes(includeDeletes);
compact.setConfig(configureReftable(reftableConfig, out));
+ if(refLogExpire != null ){
+ compact.setReflogExpireOldestReflogTimeMillis(
+ refLogExpire.toEpochMilli());
+ }
compact.compact();
pack.addFileExt(REFTABLE);
pack.setReftableStats(compact.getStats());
@@ -754,18 +789,17 @@ public class DfsGarbageCollector {
return;
}
- Set<ObjectId> allTips = refsBefore.stream().map(Ref::getObjectId)
- .collect(Collectors.toUnmodifiableSet());
-
try (DfsOutputStream out = objdb.writeFile(pack, COMMIT_GRAPH);
RevWalk pool = new RevWalk(ctx)) {
- GraphCommits gcs = GraphCommits.fromWalk(pm, allTips, pool);
+ GraphCommits gcs = GraphCommits.fromWalk(pm, allHeadsAndTags, pool);
CountingOutputStream cnt = new CountingOutputStream(out);
- CommitGraphWriter writer = new CommitGraphWriter(gcs);
- writer.write(pm, cnt);
+ CommitGraphWriter writer = new CommitGraphWriter(gcs,
+ writeBloomFilter);
+ CommitGraphWriter.Stats stats = writer.write(pm, cnt);
pack.addFileExt(COMMIT_GRAPH);
pack.setFileSize(COMMIT_GRAPH, cnt.getCount());
pack.setBlockSize(COMMIT_GRAPH, out.blockSize());
+ pack.setCommitGraphStats(stats);
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java
index a2fb67ff0b..dd9e4b96a4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java
@@ -11,6 +11,7 @@
package org.eclipse.jgit.internal.storage.dfs;
import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.OBJECT_SIZE_INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
import static org.eclipse.jgit.lib.Constants.OBJ_OFS_DELTA;
import static org.eclipse.jgit.lib.Constants.OBJ_REF_DELTA;
@@ -40,11 +41,13 @@ import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.file.BasePackIndexWriter;
import org.eclipse.jgit.internal.storage.file.PackIndex;
-import org.eclipse.jgit.internal.storage.file.PackIndexWriter;
+import org.eclipse.jgit.internal.storage.file.PackObjectSizeIndexWriter;
import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdOwnerMap;
@@ -68,6 +71,8 @@ public class DfsInserter extends ObjectInserter {
private static final int INDEX_VERSION = 2;
final DfsObjDatabase db;
+
+ private final int minBytesForObjectSizeIndex;
int compression = Deflater.BEST_COMPRESSION;
List<PackedObjectInfo> objectList;
@@ -78,7 +83,6 @@ public class DfsInserter extends ObjectInserter {
DfsPackDescription packDsc;
PackStream packOut;
private boolean rollback;
- private boolean checkExisting = true;
/**
* Initialize a new inserter.
@@ -88,37 +92,24 @@ public class DfsInserter extends ObjectInserter {
*/
protected DfsInserter(DfsObjDatabase db) {
this.db = db;
- }
-
- /**
- * Check existence
- *
- * @param check
- * if {@code false}, will write out possibly-duplicate objects
- * without first checking whether they exist in the repo; default
- * is true.
- */
- public void checkExisting(boolean check) {
- checkExisting = check;
+ this.minBytesForObjectSizeIndex = db.getRepository().getConfig().getInt(
+ ConfigConstants.CONFIG_PACK_SECTION,
+ ConfigConstants.CONFIG_KEY_MIN_BYTES_OBJ_SIZE_INDEX, -1);
}
void setCompressionLevel(int compression) {
this.compression = compression;
}
-
- /** {@inheritDoc} */
@Override
public DfsPackParser newPackParser(InputStream in) throws IOException {
return new DfsPackParser(db, this, in);
}
- /** {@inheritDoc} */
@Override
public ObjectReader newReader() {
return new Reader();
}
- /** {@inheritDoc} */
@Override
public ObjectId insert(int type, byte[] data, int off, int len)
throws IOException {
@@ -126,16 +117,16 @@ public class DfsInserter extends ObjectInserter {
if (objectMap != null && objectMap.contains(id))
return id;
// Ignore unreachable (garbage) objects here.
- if (checkExisting && db.has(id, true))
+ if (db.has(id, true)) {
return id;
+ }
long offset = beginObject(type, len);
packOut.compress.write(data, off, len);
packOut.compress.finish();
- return endObject(id, offset);
+ return endObject(id, offset, len, type);
}
- /** {@inheritDoc} */
@Override
public ObjectId insert(int type, long len, InputStream in)
throws IOException {
@@ -152,16 +143,17 @@ public class DfsInserter extends ObjectInserter {
md.update(Constants.encodeASCII(len));
md.update((byte) 0);
- while (0 < len) {
- int n = in.read(buf, 0, (int) Math.min(buf.length, len));
+ long inLength = len;
+ while (0 < inLength) {
+ int n = in.read(buf, 0, (int) Math.min(buf.length, inLength));
if (n <= 0)
throw new EOFException();
md.update(buf, 0, n);
packOut.compress.write(buf, 0, n);
- len -= n;
+ inLength -= n;
}
packOut.compress.finish();
- return endObject(md.toObjectId(), offset);
+ return endObject(md.toObjectId(), offset, len, type);
}
private byte[] insertBuffer(long len) {
@@ -178,7 +170,6 @@ public class DfsInserter extends ObjectInserter {
return buf;
}
- /** {@inheritDoc} */
@Override
public void flush() throws IOException {
if (packDsc == null)
@@ -196,17 +187,17 @@ public class DfsInserter extends ObjectInserter {
sortObjectsById();
PackIndex index = writePackIndex(packDsc, packHash, objectList);
+ writeObjectSizeIndex(packDsc, objectList);
db.commitPack(Collections.singletonList(packDsc), null);
rollback = false;
- DfsPackFile p = new DfsPackFile(cache, packDsc);
+ DfsPackFile p = db.createDfsPackFile(cache, packDsc);
if (index != null)
p.setPackIndex(index);
db.addPack(p);
clear();
}
- /** {@inheritDoc} */
@Override
public void close() {
if (packOut != null) {
@@ -244,10 +235,12 @@ public class DfsInserter extends ObjectInserter {
return offset;
}
- private ObjectId endObject(ObjectId id, long offset) {
+ private ObjectId endObject(ObjectId id, long offset, long inflatedSize, int type) {
PackedObjectInfo obj = new PackedObjectInfo(id);
+ obj.setType(type);
obj.setOffset(offset);
obj.setCRC((int) packOut.crc32.getValue());
+ obj.setFullSize(inflatedSize);
objectList.add(obj);
objectMap.addIfAbsent(obj);
return id;
@@ -314,7 +307,23 @@ public class DfsInserter extends ObjectInserter {
private static void index(OutputStream out, byte[] packHash,
List<PackedObjectInfo> list) throws IOException {
- PackIndexWriter.createVersion(out, INDEX_VERSION).write(list, packHash);
+ BasePackIndexWriter.createVersion(out, INDEX_VERSION).write(list, packHash);
+ }
+
+ void writeObjectSizeIndex(DfsPackDescription pack,
+ List<PackedObjectInfo> packedObjs) throws IOException {
+ if (minBytesForObjectSizeIndex < 0) {
+ return;
+ }
+ try (DfsOutputStream os = db.writeFile(pack, PackExt.OBJECT_SIZE_INDEX);
+ CountingOutputStream cnt = new CountingOutputStream(os)) {
+ PackObjectSizeIndexWriter
+ .createWriter(os, minBytesForObjectSizeIndex)
+ .write(packedObjs);
+ pack.addFileExt(OBJECT_SIZE_INDEX);
+ pack.setBlockSize(OBJECT_SIZE_INDEX, os.blockSize());
+ pack.setFileSize(OBJECT_SIZE_INDEX, cnt.getCount());
+ }
}
private class PackStream extends OutputStream {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java
index 46ec87df54..1a873d1204 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java
@@ -11,6 +11,8 @@
package org.eclipse.jgit.internal.storage.dfs;
import static java.util.stream.Collectors.joining;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -26,11 +28,16 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
+import org.eclipse.jgit.internal.storage.file.BasePackIndexWriter;
+import org.eclipse.jgit.internal.storage.file.PackBitmapIndexWriterV1;
+import org.eclipse.jgit.internal.storage.pack.PackIndexWriter;
+import org.eclipse.jgit.internal.storage.pack.PackBitmapIndexWriter;
import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectDatabase;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.util.io.CountingOutputStream;
/**
* Manages objects stored in
@@ -45,16 +52,6 @@ public abstract class DfsObjDatabase extends ObjectDatabase {
boolean dirty() {
return true;
}
-
- @Override
- void clearDirty() {
- // Always dirty.
- }
-
- @Override
- public void markDirty() {
- // Always dirty.
- }
};
/**
@@ -241,13 +238,11 @@ public abstract class DfsObjDatabase extends ObjectDatabase {
this.packComparator = packComparator;
}
- /** {@inheritDoc} */
@Override
public DfsReader newReader() {
return new DfsReader(this);
}
- /** {@inheritDoc} */
@Override
public ObjectInserter newInserter() {
return new DfsInserter(this);
@@ -372,7 +367,7 @@ public abstract class DfsObjDatabase extends ObjectDatabase {
* Default implementation of this method would be equivalent to
* {@code newPack(source).setEstimatedPackSize(estimatedPackSize)}. But the
* clients can override this method to use the given
- * {@code estomatedPackSize} value more efficiently in the process of
+ * {@code estimatedPackSize} value more efficiently in the process of
* creating a new
* {@link org.eclipse.jgit.internal.storage.dfs.DfsPackDescription} object.
*
@@ -529,7 +524,7 @@ public abstract class DfsObjDatabase extends ObjectDatabase {
DfsPackFile[] packs = new DfsPackFile[1 + o.packs.length];
packs[0] = newPack;
System.arraycopy(o.packs, 0, packs, 1, o.packs.length);
- n = new PackListImpl(packs, o.reftables);
+ n = new PackList(packs, o.reftables);
} while (!packList.compareAndSet(o, n));
}
@@ -554,7 +549,7 @@ public abstract class DfsObjDatabase extends ObjectDatabase {
}
}
tables.add(new DfsReftable(add));
- n = new PackListImpl(o.packs, tables.toArray(new DfsReftable[0]));
+ n = new PackList(o.packs, tables.toArray(new DfsReftable[0]));
} while (!packList.compareAndSet(o, n));
}
@@ -594,7 +589,7 @@ public abstract class DfsObjDatabase extends ObjectDatabase {
if (oldPack != null) {
newPacks.add(oldPack);
} else if (dsc.hasFileExt(PackExt.PACK)) {
- newPacks.add(new DfsPackFile(cache, dsc));
+ newPacks.add(createDfsPackFile(cache, dsc));
foundNew = true;
}
@@ -608,17 +603,33 @@ public abstract class DfsObjDatabase extends ObjectDatabase {
}
if (newPacks.isEmpty() && newReftables.isEmpty())
- return new PackListImpl(NO_PACKS.packs, NO_PACKS.reftables);
+ return new PackList(NO_PACKS.packs, NO_PACKS.reftables);
if (!foundNew) {
- old.clearDirty();
return old;
}
Collections.sort(newReftables, reftableComparator());
- return new PackListImpl(
+ return new PackList(
newPacks.toArray(new DfsPackFile[0]),
newReftables.toArray(new DfsReftable[0]));
}
+ /**
+ * Create instances of DfsPackFile
+ *
+ * Implementors can decide to construct or wrap DfsPackFile in different
+ * ways.
+ *
+ * @param cache
+ * block cache
+ * @param dsc
+ * pack description
+ * @return the dfs packfile
+ */
+ protected DfsPackFile createDfsPackFile(DfsBlockCache cache,
+ DfsPackDescription dsc) {
+ return new DfsPackFile(cache, dsc);
+ }
+
private static Map<DfsPackDescription, DfsPackFile> packMap(PackList old) {
Map<DfsPackDescription, DfsPackFile> forReuse = new HashMap<>();
for (DfsPackFile p : old.packs) {
@@ -657,14 +668,13 @@ public abstract class DfsObjDatabase extends ObjectDatabase {
packList.set(NO_PACKS);
}
- /** {@inheritDoc} */
@Override
public void close() {
packList.set(NO_PACKS);
}
/** Snapshot of packs scanned in a single pass. */
- public abstract static class PackList {
+ public static class PackList {
/** All known packs, sorted. */
public final DfsPackFile[] packs;
@@ -678,7 +688,11 @@ public abstract class DfsObjDatabase extends ObjectDatabase {
this.reftables = reftables;
}
- /** @return last modified time of all packs, in milliseconds. */
+ /**
+ * Get last modified time of all packs
+ *
+ * @return last modified time of all packs, in milliseconds.
+ */
public long getLastModified() {
if (lastModified < 0) {
long max = 0;
@@ -690,39 +704,63 @@ public abstract class DfsObjDatabase extends ObjectDatabase {
return lastModified;
}
- abstract boolean dirty();
- abstract void clearDirty();
-
- /**
- * Mark pack list as dirty.
- * <p>
- * Used when the caller knows that new data might have been written to the
- * repository that could invalidate open readers depending on this pack list,
- * for example if refs are newly scanned.
- */
- public abstract void markDirty();
- }
-
- private static final class PackListImpl extends PackList {
- private volatile boolean dirty;
-
- PackListImpl(DfsPackFile[] packs, DfsReftable[] reftables) {
- super(packs, reftables);
- }
-
- @Override
boolean dirty() {
- return dirty;
+ return false;
}
+ }
- @Override
- void clearDirty() {
- dirty = false;
- }
+ /**
+ * Returns a writer to store the bitmap index in this object database.
+ *
+ * @param pack
+ * Pack file to which the bitmaps are associated.
+ * @return a writer to store bitmaps associated with the pack
+ * @throws IOException
+ * when some I/O problem occurs while creating or writing to
+ * output stream
+ */
+ public PackBitmapIndexWriter getPackBitmapIndexWriter(
+ DfsPackDescription pack) throws IOException {
+ return (bitmaps, packDataChecksum) -> {
+ try (DfsOutputStream out = writeFile(pack, BITMAP_INDEX)) {
+ CountingOutputStream cnt = new CountingOutputStream(out);
+ PackBitmapIndexWriterV1 iw = new PackBitmapIndexWriterV1(cnt);
+ iw.write(bitmaps, packDataChecksum);
+ pack.addFileExt(BITMAP_INDEX);
+ pack.setFileSize(BITMAP_INDEX, cnt.getCount());
+ pack.setBlockSize(BITMAP_INDEX, out.blockSize());
+ }
+ };
+ }
- @Override
- public void markDirty() {
- dirty = true;
- }
+ /**
+ * Returns a writer to store the pack index in this object database.
+ *
+ * @param pack
+ * Pack file to which the index is associated.
+ * @param indexVersion
+ * which version of the index to write
+ * @return a writer to store the index associated with the pack
+ * @throws IOException
+ * when some I/O problem occurs while creating or writing to
+ * output stream
+ */
+ public PackIndexWriter getPackIndexWriter(
+ DfsPackDescription pack, int indexVersion)
+ throws IOException {
+ return (objectsToStore, packDataChecksum) -> {
+ try (DfsOutputStream out = writeFile(pack, INDEX);
+ CountingOutputStream cnt = new CountingOutputStream(out)) {
+ final PackIndexWriter iw = BasePackIndexWriter
+ .createVersion(cnt,
+ indexVersion);
+ iw.write(objectsToStore, packDataChecksum);
+ pack.addFileExt(INDEX);
+ pack.setFileSize(INDEX, cnt.getCount());
+ pack.setBlockSize(INDEX, out.blockSize());
+ pack.setIndexVersion(indexVersion);
+ }
+ };
}
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectRepresentation.java
index 8e124e3c20..885582fae2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectRepresentation.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectRepresentation.java
@@ -24,25 +24,21 @@ class DfsObjectRepresentation extends StoredObjectRepresentation {
this.pack = pack;
}
- /** {@inheritDoc} */
@Override
public int getFormat() {
return format;
}
- /** {@inheritDoc} */
@Override
public int getWeight() {
return (int) Math.min(length, Integer.MAX_VALUE);
}
- /** {@inheritDoc} */
@Override
public ObjectId getDeltaBase() {
return baseId;
}
- /** {@inheritDoc} */
@Override
public boolean wasDeltaAttempted() {
switch (pack.getPackDescription().getPackSource()) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectToPack.java
index 10fa2365bd..c2feab8cd3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectToPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjectToPack.java
@@ -39,14 +39,12 @@ class DfsObjectToPack extends ObjectToPack {
setExtendedFlag(FLAG_FOUND);
}
- /** {@inheritDoc} */
@Override
protected void clearReuseAsIs() {
super.clearReuseAsIs();
pack = null;
}
- /** {@inheritDoc} */
@Override
public void select(StoredObjectRepresentation ref) {
DfsObjectRepresentation ptr = (DfsObjectRepresentation) ref;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsOutputStream.java
index 7cf91a4882..c74a1e1d7d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsOutputStream.java
@@ -39,13 +39,11 @@ public abstract class DfsOutputStream extends OutputStream {
return 0;
}
- /** {@inheritDoc} */
@Override
public void write(int b) throws IOException {
write(new byte[] { (byte) b });
}
- /** {@inheritDoc} */
@Override
public abstract void write(byte[] buf, int off, int len) throws IOException;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
index 86144b389c..6339b0326a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java
@@ -12,7 +12,7 @@ package org.eclipse.jgit.internal.storage.dfs;
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.COMPACT;
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC;
-import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.OBJECT_SIZE_INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
import static org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation.PACK_DELTA;
@@ -249,6 +249,7 @@ public class DfsPackCompactor {
try {
writePack(objdb, outDesc, pw, pm);
writeIndex(objdb, outDesc, pw);
+ writeObjectSizeIndex(objdb, outDesc, pw);
PackStatistics stats = pw.getStatistics();
@@ -404,7 +405,7 @@ public class DfsPackCompactor {
pw.addObject(obj);
obj.add(added);
- src.representation(rep, id.offset, ctx, rev);
+ src.fillRepresentation(rep, id.offset, ctx, rev);
if (rep.getFormat() != PACK_DELTA)
continue;
@@ -458,13 +459,20 @@ public class DfsPackCompactor {
private static void writeIndex(DfsObjDatabase objdb,
DfsPackDescription pack,
PackWriter pw) throws IOException {
- try (DfsOutputStream out = objdb.writeFile(pack, INDEX)) {
+ pw.writeIndex(objdb.getPackIndexWriter(pack, pw.getIndexVersion()));
+ }
+
+ private static void writeObjectSizeIndex(DfsObjDatabase objdb,
+ DfsPackDescription pack,
+ PackWriter pw) throws IOException {
+ try (DfsOutputStream out = objdb.writeFile(pack, OBJECT_SIZE_INDEX)) {
CountingOutputStream cnt = new CountingOutputStream(out);
- pw.writeIndex(cnt);
- pack.addFileExt(INDEX);
- pack.setFileSize(INDEX, cnt.getCount());
- pack.setBlockSize(INDEX, out.blockSize());
- pack.setIndexVersion(pw.getIndexVersion());
+ pw.writeObjectSizeIndex(cnt);
+ if (cnt.getCount() > 0) {
+ pack.addFileExt(OBJECT_SIZE_INDEX);
+ pack.setFileSize(OBJECT_SIZE_INDEX, cnt.getCount());
+ pack.setBlockSize(OBJECT_SIZE_INDEX, out.blockSize());
+ }
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java
index 4f418ab4db..663190a233 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackDescription.java
@@ -17,6 +17,7 @@ import java.util.Arrays;
import java.util.Comparator;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.internal.storage.commitgraph.CommitGraphWriter;
import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
@@ -144,6 +145,7 @@ public class DfsPackDescription {
private PackStatistics packStats;
private ReftableWriter.Stats refStats;
+ private CommitGraphWriter.Stats commitGraphStats;
private int extensions;
private int indexVersion;
private long estimatedPackSize;
@@ -481,6 +483,19 @@ public class DfsPackDescription {
}
/**
+ * Get stats from the sibling commit graph, if created.
+ *
+ * @return stats from the sibling commit graph, if created.
+ */
+ public CommitGraphWriter.Stats getCommitGraphStats() {
+ return commitGraphStats;
+ }
+
+ void setCommitGraphStats(CommitGraphWriter.Stats stats) {
+ this.commitGraphStats = stats;
+ }
+
+ /**
* Discard the pack statistics, if it was populated.
*
* @return {@code this}
@@ -512,13 +527,11 @@ public class DfsPackDescription {
return this;
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
return packName.hashCode();
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object b) {
if (b instanceof DfsPackDescription) {
@@ -539,7 +552,6 @@ public class DfsPackDescription {
}
}
- /** {@inheritDoc} */
@Override
public String toString() {
return getFileName(PackExt.PACK);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
index c745b8e6ee..05b63eaca1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java
@@ -16,6 +16,7 @@ import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UN
import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.COMMIT_GRAPH;
import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.OBJECT_SIZE_INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
import static org.eclipse.jgit.internal.storage.pack.PackExt.REVERSE_INDEX;
@@ -26,8 +27,12 @@ import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.text.MessageFormat;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.CRC32;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
@@ -42,16 +47,23 @@ import org.eclipse.jgit.internal.storage.commitgraph.CommitGraph;
import org.eclipse.jgit.internal.storage.commitgraph.CommitGraphLoader;
import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.internal.storage.file.PackObjectSizeIndex;
+import org.eclipse.jgit.internal.storage.file.PackObjectSizeIndexLoader;
import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
+import org.eclipse.jgit.internal.storage.file.PackReverseIndexFactory;
import org.eclipse.jgit.internal.storage.pack.BinaryDelta;
+import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
import org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.util.BlockList;
import org.eclipse.jgit.util.LongList;
/**
@@ -61,8 +73,18 @@ import org.eclipse.jgit.util.LongList;
*/
public final class DfsPackFile extends BlockBasedFile {
private static final int REC_SIZE = Constants.OBJECT_ID_LENGTH + 8;
+
private static final long REF_POSITION = 0;
+ private static final Comparator<DfsObjectToPack> OFFSET_SORT = (
+ DfsObjectToPack a,
+ DfsObjectToPack b) -> Long.signum(a.getOffset() - b.getOffset());
+
+ /**
+ * Loader for the default file-based {@link PackBitmapIndex} implementation.
+ */
+ public static final PackBitmapIndexLoader DEFAULT_BITMAP_LOADER = new StreamPackBitmapIndexLoader();
+
/** Index mapping {@link ObjectId} to position within the pack stream. */
private volatile PackIndex index;
@@ -75,6 +97,13 @@ public final class DfsPackFile extends BlockBasedFile {
/** Index of compressed commit graph mapping entire object graph. */
private volatile CommitGraph commitGraph;
+ private final PackBitmapIndexLoader bitmapLoader;
+
+ /** Index by size */
+ private boolean objectSizeIndexLoadAttempted;
+
+ private volatile PackObjectSizeIndex objectSizeIndex;
+
/**
* Objects we have tried to read, and discovered to be corrupt.
* <p>
@@ -87,6 +116,57 @@ public final class DfsPackFile extends BlockBasedFile {
/** Lock for {@link #corruptObjects}. */
private final Object corruptObjectsLock = new Object();
+ private final IndexFactory indexFactory;
+
+ /**
+ * Returns the indexes for this pack.
+ * <p>
+ * We define indexes in different sub interfaces to allow implementing the
+ * indexes over different combinations of backends.
+ * <p>
+ * Implementations decide if/how to cache the indexes. The calling
+ * DfsPackFile will keep the reference to the index as long as it needs it.
+ */
+ public interface IndexFactory {
+ /**
+ * Take care of loading the primary and reverse indexes for this pack.
+ */
+ interface PackIndexes {
+ /**
+ * Load the primary index for the pack.
+ *
+ * @param ctx
+ * reader to find the raw bytes
+ * @return a primary index
+ * @throws IOException
+ * a problem finding/parsing the index
+ */
+ PackIndex index(DfsReader ctx) throws IOException;
+
+ /**
+ * Load the reverse index of the pack
+ *
+ * @param ctx
+ * reader to find the raw bytes
+ * @param idx
+ * primary index for this reverse index (probably loaded
+ * via #index(DfsReader))
+ * @return the reverse index of the pack
+ * @throws IOException
+ * a problem finding/parsing the reverse index
+ */
+ PackReverseIndex reverseIndex(DfsReader ctx, PackIndex idx)
+ throws IOException;
+ }
+
+ /**
+ * Returns a provider of the primary and reverse indexes of this pack
+ *
+ * @return an implementation of the {@link PackIndexes} interface
+ */
+ PackIndexes getPackIndexes();
+ }
+
/**
* Construct a reader for an existing, packfile.
*
@@ -96,6 +176,24 @@ public final class DfsPackFile extends BlockBasedFile {
* description of the pack within the DFS.
*/
DfsPackFile(DfsBlockCache cache, DfsPackDescription desc) {
+ this(cache, desc, DEFAULT_BITMAP_LOADER,
+ new CachedStreamIndexFactory(cache, desc));
+ }
+
+ /**
+ * Create an instance of DfsPackFile with a custom bitmap loader
+ *
+ * @param cache
+ * cache that owns the pack data
+ * @param desc
+ * description of the pack within the DFS
+ * @param bitmapLoader
+ * loader to get the bitmaps of this pack (if any)
+ * @param indexFactory
+ * an IndexFactory to get references to the indexes of this pack
+ */
+ public DfsPackFile(DfsBlockCache cache, DfsPackDescription desc,
+ PackBitmapIndexLoader bitmapLoader, IndexFactory indexFactory) {
super(cache, desc, PACK);
int bs = desc.getBlockSize(PACK);
@@ -105,6 +203,9 @@ public final class DfsPackFile extends BlockBasedFile {
long sz = desc.getFileSize(PACK);
length = sz > 0 ? sz : -1;
+
+ this.bitmapLoader = bitmapLoader;
+ this.indexFactory = indexFactory;
}
/**
@@ -159,20 +260,12 @@ public final class DfsPackFile extends BlockBasedFile {
Repository.getGlobalListenerList()
.dispatch(new BeforeDfsPackIndexLoadedEvent(this));
try {
- DfsStreamKey idxKey = desc.getStreamKey(INDEX);
- AtomicBoolean cacheHit = new AtomicBoolean(true);
- DfsBlockCache.Ref<PackIndex> idxref = cache.getOrLoadRef(idxKey,
- REF_POSITION, () -> {
- cacheHit.set(false);
- return loadPackIndex(ctx, idxKey);
- });
- if (cacheHit.get()) {
- ctx.stats.idxCacheHit++;
- }
- PackIndex idx = idxref.get();
- if (index == null && idx != null) {
- index = idx;
+ index = indexFactory.getPackIndexes().index(ctx);
+ if (index == null) {
+ throw new IOException(
+ "Couldn't get a reference to the primary index"); //$NON-NLS-1$
}
+ ctx.emitIndexLoad(desc, INDEX, index);
return index;
} catch (IOException e) {
invalid = true;
@@ -196,7 +289,7 @@ public final class DfsPackFile extends BlockBasedFile {
* the bitmap index is not available, or is corrupt.
*/
public PackBitmapIndex getBitmapIndex(DfsReader ctx) throws IOException {
- if (invalid || isGarbage() || !desc.hasFileExt(BITMAP_INDEX)) {
+ if (invalid || isGarbage() || !bitmapLoader.hasBitmaps(desc)) {
return null;
}
@@ -204,6 +297,15 @@ public final class DfsPackFile extends BlockBasedFile {
return bitmapIndex;
}
+ if (!bitmapLoader.keepInDfs(desc)) {
+ PackBitmapIndexLoader.LoadResult result = bitmapLoader
+ .loadPackBitmapIndex(ctx, this);
+ if (bitmapIndex == null && result.bitmapIndex != null) {
+ bitmapIndex = result.bitmapIndex;
+ }
+ return bitmapIndex;
+ }
+
DfsStreamKey bitmapKey = desc.getStreamKey(BITMAP_INDEX);
AtomicBoolean cacheHit = new AtomicBoolean(true);
DfsBlockCache.Ref<PackBitmapIndex> idxref = cache
@@ -218,6 +320,7 @@ public final class DfsPackFile extends BlockBasedFile {
if (bitmapIndex == null && bmidx != null) {
bitmapIndex = bmidx;
}
+ ctx.emitIndexLoad(desc, BITMAP_INDEX, bitmapIndex);
return bitmapIndex;
}
@@ -255,30 +358,71 @@ public final class DfsPackFile extends BlockBasedFile {
if (commitGraph == null && cg != null) {
commitGraph = cg;
}
+ ctx.emitIndexLoad(desc, COMMIT_GRAPH, commitGraph);
return commitGraph;
}
- PackReverseIndex getReverseIdx(DfsReader ctx) throws IOException {
+ /**
+ * Get the PackReverseIndex for this PackFile.
+ *
+ * @param ctx
+ * reader context to support reading from the backing store if
+ * the index is not already loaded in memory
+ * @return the PackReverseIndex
+ * @throws java.io.IOException
+ * the pack index is not available, or is corrupt
+ */
+ public PackReverseIndex getReverseIdx(DfsReader ctx) throws IOException {
if (reverseIndex != null) {
return reverseIndex;
}
- PackIndex idx = idx(ctx);
- DfsStreamKey revKey = desc.getStreamKey(REVERSE_INDEX);
+ reverseIndex = indexFactory.getPackIndexes().reverseIndex(ctx, idx(ctx));
+ if (reverseIndex == null) {
+ throw new IOException(
+ "Couldn't get a reference to the reverse index"); //$NON-NLS-1$
+ }
+ ctx.emitIndexLoad(desc, REVERSE_INDEX, reverseIndex);
+ return reverseIndex;
+ }
+
+ private PackObjectSizeIndex getObjectSizeIndex(DfsReader ctx)
+ throws IOException {
+ if (objectSizeIndex != null) {
+ return objectSizeIndex;
+ }
+
+ if (objectSizeIndexLoadAttempted
+ || !ctx.getOptions().shouldUseObjectSizeIndex()
+ || !desc.hasFileExt(OBJECT_SIZE_INDEX)) {
+ // Pack doesn't have object size index
+ return null;
+ }
+
+ DfsStreamKey objSizeKey = desc.getStreamKey(OBJECT_SIZE_INDEX);
AtomicBoolean cacheHit = new AtomicBoolean(true);
- DfsBlockCache.Ref<PackReverseIndex> revref = cache.getOrLoadRef(revKey,
- REF_POSITION, () -> {
- cacheHit.set(false);
- return loadReverseIdx(ctx, revKey, idx);
- });
- if (cacheHit.get()) {
- ctx.stats.ridxCacheHit++;
+ try {
+ DfsBlockCache.Ref<PackObjectSizeIndex> sizeIdxRef = cache
+ .getOrLoadRef(objSizeKey, REF_POSITION, () -> {
+ cacheHit.set(false);
+ return loadObjectSizeIndex(ctx, objSizeKey);
+ });
+ if (cacheHit.get()) {
+ ctx.stats.objectSizeIndexCacheHit++;
+ }
+ PackObjectSizeIndex sizeIdx = sizeIdxRef.get();
+ if (objectSizeIndex == null && sizeIdx != null) {
+ objectSizeIndex = sizeIdx;
+ }
+ } finally {
+ objectSizeIndexLoadAttempted = true;
}
- PackReverseIndex revidx = revref.get();
- if (reverseIndex == null && revidx != null) {
- reverseIndex = revidx;
+
+ // Object size index is optional, it can be null and that's fine
+ if (objectSizeIndex != null) {
+ ctx.emitIndexLoad(desc, OBJECT_SIZE_INDEX, objectSizeIndex);
}
- return reverseIndex;
+ return objectSizeIndex;
}
/**
@@ -298,6 +442,10 @@ public final class DfsPackFile extends BlockBasedFile {
return 0 < offset && !isCorrupt(offset);
}
+ int findIdxPosition(DfsReader ctx, AnyObjectId id) throws IOException {
+ return idx(ctx).findPosition(id);
+ }
+
/**
* Get an object from this pack.
*
@@ -320,23 +468,43 @@ public final class DfsPackFile extends BlockBasedFile {
return idx(ctx).findOffset(id);
}
- void resolve(DfsReader ctx, Set<ObjectId> matches, AbbreviatedObjectId id,
- int matchLimit) throws IOException {
- idx(ctx).resolve(matches, id, matchLimit);
- }
-
/**
- * Obtain the total number of objects available in this pack. This method
- * relies on pack index, giving number of effectively available objects.
+ * Return objects in the list available in this pack, sorted in (pack,
+ * offset) order.
*
* @param ctx
- * current reader for the calling thread.
- * @return number of objects in index of this pack, likewise in this pack
+ * a reader
+ * @param objects
+ * objects we are looking for
+ * @param skipFound
+ * ignore objects already found.
+ * @return list of objects with pack and offset set.
* @throws IOException
- * the index file cannot be loaded into memory.
+ * an error occurred
*/
- long getObjectCount(DfsReader ctx) throws IOException {
- return idx(ctx).getObjectCount();
+ List<DfsObjectToPack> findAllFromPack(DfsReader ctx,
+ Iterable<ObjectToPack> objects, boolean skipFound)
+ throws IOException {
+ List<DfsObjectToPack> tmp = new BlockList<>();
+ for (ObjectToPack obj : objects) {
+ DfsObjectToPack otp = (DfsObjectToPack) obj;
+ if (skipFound && otp.isFound()) {
+ continue;
+ }
+ long p = idx(ctx).findOffset(otp);
+ if (p <= 0 || isCorrupt(p)) {
+ continue;
+ }
+ otp.setOffset(p);
+ tmp.add(otp);
+ }
+ Collections.sort(tmp, OFFSET_SORT);
+ return tmp;
+ }
+
+ void resolve(DfsReader ctx, Set<ObjectId> matches, AbbreviatedObjectId id,
+ int matchLimit) throws IOException {
+ idx(ctx).resolve(matches, id, matchLimit);
}
private byte[] decompress(long position, int sz, DfsReader ctx)
@@ -374,6 +542,7 @@ public final class DfsPackFile extends BlockBasedFile {
if (sz > 0) {
rc.setReadAheadBytes(sz);
}
+ //TODO(ifrade): report ctx.emitBlockLoaded for this copy
if (cache.shouldCopyThroughCache(length)) {
copyPackThroughCache(out, ctx, rc);
} else {
@@ -399,6 +568,7 @@ public final class DfsPackFile extends BlockBasedFile {
}
}
+ @SuppressWarnings("ByteBufferBackingArray")
private long copyPackBypassCache(PackOutputStream out, ReadableChannel rc)
throws IOException {
ByteBuffer buf = newCopyBuffer(out, rc);
@@ -957,12 +1127,120 @@ public final class DfsPackFile extends BlockBasedFile {
}
}
- void representation(DfsObjectRepresentation r, final long pos,
+ /**
+ * Return if this packfile has an object size index attached.
+ *
+ * This loads the index if it is not already in memory.
+ *
+ * @param ctx
+ * reader context to support reading from the backing store if
+ * the object size index is not already loaded in memory.
+ * @return true if the packfile has an object size index.
+ * @throws IOException
+ * a problem accessing storage while looking for the index
+ */
+ boolean hasObjectSizeIndex(DfsReader ctx) throws IOException {
+ return getObjectSizeIndex(ctx) != null;
+ }
+
+ /**
+ * Return minimum size for an object to be included in the object size
+ * index.
+ *
+ * Caller must make sure the pack has an object size index with
+ * {@link #hasObjectSizeIndex} before calling this method.
+ *
+ * @param ctx
+ * reader context to support reading from the backing store if
+ * the object size index is not already loaded in memory.
+ * @return minimum size for indexing in bytes
+ * @throws IOException
+ * no object size index or a problem loading it.
+ */
+ int getObjectSizeIndexThreshold(DfsReader ctx) throws IOException {
+ PackObjectSizeIndex idx = getObjectSizeIndex(ctx);
+ if (idx == null) {
+ throw new IOException("Asking threshold of non-existing obj-size"); //$NON-NLS-1$
+ }
+ return idx.getThreshold();
+ }
+
+ /**
+ * Return the size of the object from the object-size index. The object
+ * should be a blob. Any other type is not indexed and returns -1.
+ * <p>
+ * Caller MUST pass a valid index position, as returned by
+ * {@link #findIdxPosition(DfsReader, AnyObjectId)} and verify the pack has
+ * object size index (e.g. with {@link #hasObjectSizeIndex(DfsReader)})
+ * before asking the indexed size.
+ *
+ * @param ctx
+ * reader context to support reading from the backing store if
+ * the object size index is not already loaded in memory.
+ * @param idxPosition
+ * position in the primary index of the object we are looking
+ * for, as returned by findIdxPosition
+ * @return size of the object from the index. Negative if object is not in
+ * the index (below threshold or not a blob)
+ * @throws IOException
+ * could not read the object size index. IO problem or the pack
+ * doesn't have it.
+ */
+ long getIndexedObjectSize(DfsReader ctx, int idxPosition)
+ throws IOException {
+ if (idxPosition < 0) {
+ throw new IllegalArgumentException("Invalid index position"); //$NON-NLS-1$
+ }
+ PackObjectSizeIndex sizeIdx = getObjectSizeIndex(ctx);
+ if (sizeIdx == null) {
+ throw new IllegalStateException(
+ "Asking indexed size from a pack without object size index"); //$NON-NLS-1$
+ }
+
+ return sizeIdx.getSize(idxPosition);
+ }
+
+ /**
+ * Populates the representation object with the details of how the object at
+ * "pos" is stored in this pack (e.g. whole or deltified, its packed
+ * length).
+ *
+ * @param r
+ * represention object to carry data
+ * @param offset
+ * offset in this pack of the object
+ * @param ctx
+ * a reader
+ * @throws IOException
+ * an error reading the object from disk
+ */
+ void fillRepresentation(DfsObjectRepresentation r, long offset,
+ DfsReader ctx) throws IOException {
+ fillRepresentation(r, offset, ctx, getReverseIdx(ctx));
+ }
+
+ /**
+ * Populates the representation object with the details of how the object at
+ * "pos" is stored in this pack (e.g. whole or deltified, its packed
+ * length).
+ *
+ * @param r
+ * represention object to carry data
+ * @param offset
+ * offset in this pack of the object
+ * @param ctx
+ * a reader
+ * @param rev
+ * reverse index of this pack
+ * @throws IOException
+ * an error reading the object from disk
+ */
+ void fillRepresentation(DfsObjectRepresentation r, long offset,
DfsReader ctx, PackReverseIndex rev)
throws IOException {
- r.offset = pos;
+ r.offset = offset;
final byte[] ib = ctx.tempId;
- readFully(pos, ib, 0, 20, ctx);
+ readFully(offset, ib, 0, 20, ctx);
int c = ib[0] & 0xff;
int p = 1;
final int typeCode = (c >> 4) & 7;
@@ -970,7 +1248,7 @@ public final class DfsPackFile extends BlockBasedFile {
c = ib[p++] & 0xff;
}
- long len = rev.findNextOffset(pos, length - 20) - pos;
+ long len = rev.findNextOffset(offset, length - 20) - offset;
switch (typeCode) {
case Constants.OBJ_COMMIT:
case Constants.OBJ_TREE:
@@ -991,13 +1269,13 @@ public final class DfsPackFile extends BlockBasedFile {
ofs += (c & 127);
}
r.format = StoredObjectRepresentation.PACK_DELTA;
- r.baseId = rev.findObject(pos - ofs);
+ r.baseId = rev.findObject(offset - ofs);
r.length = len - p;
return;
}
case Constants.OBJ_REF_DELTA: {
- readFully(pos + p, ib, 0, 20, ctx);
+ readFully(offset + p, ib, 0, 20, ctx);
r.format = StoredObjectRepresentation.PACK_DELTA;
r.baseId = ObjectId.fromRaw(ib);
r.length = len - p - 20;
@@ -1036,87 +1314,66 @@ public final class DfsPackFile extends BlockBasedFile {
}
}
- private DfsBlockCache.Ref<PackIndex> loadPackIndex(
- DfsReader ctx, DfsStreamKey idxKey) throws IOException {
- try {
- ctx.stats.readIdx++;
- long start = System.nanoTime();
- try (ReadableChannel rc = ctx.db.openFile(desc, INDEX)) {
- PackIndex idx = PackIndex.read(alignTo8kBlocks(rc));
- ctx.stats.readIdxBytes += rc.position();
- index = idx;
- return new DfsBlockCache.Ref<>(
- idxKey,
- REF_POSITION,
- idx.getObjectCount() * REC_SIZE,
- idx);
- } finally {
- ctx.stats.readIdxMicros += elapsedMicros(start);
+ private DfsBlockCache.Ref<PackObjectSizeIndex> loadObjectSizeIndex(
+ DfsReader ctx, DfsStreamKey objectSizeIndexKey) throws IOException {
+ ctx.stats.readObjectSizeIndex++;
+ long start = System.nanoTime();
+ long size = 0;
+ Exception parsingError = null;
+ try (ReadableChannel rc = ctx.db.openFile(desc, OBJECT_SIZE_INDEX)) {
+ try {
+ objectSizeIndex = PackObjectSizeIndexLoader
+ .load(Channels.newInputStream(rc));
+ size = rc.position();
+ } catch (IOException e) {
+ parsingError = e;
}
- } catch (EOFException e) {
- throw new IOException(MessageFormat.format(
- DfsText.get().shortReadOfIndex,
- desc.getFileName(INDEX)), e);
} catch (IOException e) {
- throw new IOException(MessageFormat.format(
- DfsText.get().cannotReadIndex,
- desc.getFileName(INDEX)), e);
+ // No object size index in this pack
+ return new DfsBlockCache.Ref<>(objectSizeIndexKey, REF_POSITION, 0,
+ null);
}
- }
- private DfsBlockCache.Ref<PackReverseIndex> loadReverseIdx(
- DfsReader ctx, DfsStreamKey revKey, PackIndex idx) {
- ctx.stats.readReverseIdx++;
- long start = System.nanoTime();
- PackReverseIndex revidx = new PackReverseIndex(idx);
- reverseIndex = revidx;
- ctx.stats.readReverseIdxMicros += elapsedMicros(start);
- return new DfsBlockCache.Ref<>(
- revKey,
- REF_POSITION,
- idx.getObjectCount() * 8,
- revidx);
+ if (parsingError != null) {
+ throw new IOException(
+ MessageFormat.format(DfsText.get().shortReadOfIndex,
+ desc.getFileName(OBJECT_SIZE_INDEX)),
+ parsingError);
+ }
+
+ ctx.stats.readObjectSizeIndexBytes += size;
+ ctx.stats.readObjectSizeIndexMicros += elapsedMicros(start);
+ return new DfsBlockCache.Ref<>(objectSizeIndexKey, REF_POSITION, size,
+ objectSizeIndex);
}
private DfsBlockCache.Ref<PackBitmapIndex> loadBitmapIndex(DfsReader ctx,
DfsStreamKey bitmapKey) throws IOException {
ctx.stats.readBitmap++;
long start = System.nanoTime();
- try (ReadableChannel rc = ctx.db.openFile(desc, BITMAP_INDEX)) {
- long size;
- PackBitmapIndex bmidx;
- try {
- bmidx = PackBitmapIndex.read(alignTo8kBlocks(rc),
- () -> idx(ctx), () -> getReverseIdx(ctx),
- ctx.getOptions().shouldLoadRevIndexInParallel());
- } finally {
- size = rc.position();
- ctx.stats.readBitmapIdxBytes += size;
- ctx.stats.readBitmapIdxMicros += elapsedMicros(start);
- }
- bitmapIndex = bmidx;
- return new DfsBlockCache.Ref<>(
- bitmapKey, REF_POSITION, size, bmidx);
- } catch (EOFException e) {
- throw new IOException(MessageFormat.format(
- DfsText.get().shortReadOfIndex,
- desc.getFileName(BITMAP_INDEX)), e);
- } catch (IOException e) {
- throw new IOException(MessageFormat.format(
- DfsText.get().cannotReadIndex,
- desc.getFileName(BITMAP_INDEX)), e);
- }
+ PackBitmapIndexLoader.LoadResult result = bitmapLoader
+ .loadPackBitmapIndex(ctx, this);
+ bitmapIndex = result.bitmapIndex;
+ ctx.stats.readBitmapIdxBytes += result.bytesRead;
+ ctx.stats.readBitmapIdxMicros += elapsedMicros(start);
+ return new DfsBlockCache.Ref<>(bitmapKey, REF_POSITION,
+ result.bytesRead, result.bitmapIndex);
}
private DfsBlockCache.Ref<CommitGraph> loadCommitGraph(DfsReader ctx,
DfsStreamKey cgkey) throws IOException {
ctx.stats.readCommitGraph++;
long start = System.nanoTime();
+ StoredConfig repoConfig = ctx.db.getRepository().getConfig();
+ boolean readChangedPathFilters = repoConfig.getBoolean(
+ ConfigConstants.CONFIG_COMMIT_GRAPH_SECTION,
+ ConfigConstants.CONFIG_KEY_READ_CHANGED_PATHS, false);
try (ReadableChannel rc = ctx.db.openFile(desc, COMMIT_GRAPH)) {
long size;
CommitGraph cg;
try {
- cg = CommitGraphLoader.read(alignTo8kBlocks(rc));
+ cg = CommitGraphLoader.read(alignTo8kBlocks(rc),
+ readChangedPathFilters);
} finally {
size = rc.position();
ctx.stats.readCommitGraphBytes += size;
@@ -1146,4 +1403,252 @@ public final class DfsPackFile extends BlockBasedFile {
}
return new BufferedInputStream(in, bs);
}
+
+ /**
+ * Loads the PackBitmapIndex associated with this packfile
+ */
+ public interface PackBitmapIndexLoader {
+ /**
+ * Does this pack has bitmaps associated?
+ *
+ * @param desc
+ * the pack
+ * @return true if the pack has bitmaps
+ */
+ boolean hasBitmaps(DfsPackDescription desc);
+
+ /**
+ * If the returned instance must be kept in DFS cache
+ *
+ * It should be true when the instance is expensive to load and can be
+ * reused.
+ *
+ * @param desc
+ * the pack
+ * @return true if the returned bitmap index should be kept in DFS
+ */
+ boolean keepInDfs(DfsPackDescription desc);
+
+ /**
+ * Returns a PackBitmapIndex for this pack, if the pack has bitmaps
+ * associated.
+ *
+ * @param ctx
+ * the reader
+ * @param pack
+ * the pack
+ * @return the pack bitmap index and bytes size (when applicable)
+ * @throws IOException
+ * error accessing storage
+ */
+ LoadResult loadPackBitmapIndex(DfsReader ctx, DfsPackFile pack)
+ throws IOException;
+
+ /**
+ * The instance of the pack bitmap index and the amount of bytes loaded.
+ *
+ * The bytes can be 0, if the implementation doesn't do any initial
+ * loading.
+ */
+ public class LoadResult {
+ /** The loaded {@link PackBitmapIndex}. */
+ public final PackBitmapIndex bitmapIndex;
+
+ /** The bytes read upon initial load (may be 0). */
+ public final long bytesRead;
+
+ /**
+ * Constructs the LoadResult.
+ *
+ * @param packBitmapIndex
+ * the loaded index.
+ * @param bytesRead
+ * the bytes read upon loading.
+ */
+ public LoadResult(PackBitmapIndex packBitmapIndex, long bytesRead) {
+ this.bitmapIndex = packBitmapIndex;
+ this.bytesRead = bytesRead;
+ }
+ }
+ }
+
+ /**
+ * Load the packbitmapindex from the BITMAP_INDEX pack extension
+ */
+ private static final class StreamPackBitmapIndexLoader implements PackBitmapIndexLoader {
+ @Override
+ public boolean hasBitmaps(DfsPackDescription desc) {
+ return desc.hasFileExt(BITMAP_INDEX);
+ }
+
+ @Override
+ public boolean keepInDfs(DfsPackDescription desc) {
+ return true;
+ }
+
+ @Override
+ public LoadResult loadPackBitmapIndex(DfsReader ctx, DfsPackFile pack)
+ throws IOException {
+ DfsPackDescription desc = pack.getPackDescription();
+ try (ReadableChannel rc = ctx.db.openFile(desc, BITMAP_INDEX)) {
+ long size;
+ PackBitmapIndex bmidx;
+ try {
+ bmidx = PackBitmapIndex.read(alignTo8kBlocks(rc),
+ () -> pack.idx(ctx), () -> pack.getReverseIdx(ctx),
+ ctx.getOptions().shouldLoadRevIndexInParallel());
+ } finally {
+ size = rc.position();
+ }
+ return new LoadResult(bmidx, size);
+ } catch (EOFException e) {
+ throw new IOException(
+ MessageFormat.format(DfsText.get().shortReadOfIndex,
+ desc.getFileName(BITMAP_INDEX)),
+ e);
+ } catch (IOException e) {
+ throw new IOException(
+ MessageFormat.format(DfsText.get().cannotReadIndex,
+ desc.getFileName(BITMAP_INDEX)),
+ e);
+ }
+ }
+ }
+
+ /**
+ * An index factory backed by Dfs streams and references cached in
+ * DfsBlockCache
+ */
+ public static final class CachedStreamIndexFactory implements IndexFactory {
+ private final CachedStreamPackIndexes indexes;
+
+ /**
+ * An index factory
+ *
+ * @param cache
+ * DFS block cache to use for the references
+ * @param desc
+ * This factory loads indexes for this package
+ */
+ public CachedStreamIndexFactory(DfsBlockCache cache,
+ DfsPackDescription desc) {
+ this.indexes = new CachedStreamPackIndexes(cache, desc);
+ }
+
+ @Override
+ public PackIndexes getPackIndexes() {
+ return indexes;
+ }
+ }
+
+ /**
+ * Load primary and reverse index from Dfs streams and cache the references
+ * in DfsBlockCache.
+ */
+ public static final class CachedStreamPackIndexes implements IndexFactory.PackIndexes {
+ private final DfsBlockCache cache;
+
+ private final DfsPackDescription desc;
+
+ /**
+ * An index factory
+ *
+ * @param cache
+ * DFS block cache to use for the references
+ * @param desc This factory loads indexes for this package
+ */
+ public CachedStreamPackIndexes(DfsBlockCache cache,
+ DfsPackDescription desc) {
+ this.cache = cache;
+ this.desc = desc;
+ }
+
+ @Override
+ public PackIndex index(DfsReader ctx) throws IOException {
+ DfsStreamKey idxKey = desc.getStreamKey(INDEX);
+ // Keep the value parsed in the loader, in case the Ref<> is
+ // nullified in ClockBlockCacheTable#reserveSpace
+ // before we read its value.
+ AtomicReference<PackIndex> loadedRef = new AtomicReference<>(null);
+ DfsBlockCache.Ref<PackIndex> cachedRef = cache.getOrLoadRef(idxKey,
+ REF_POSITION, () -> {
+ RefWithSize<PackIndex> idx = loadPackIndex(ctx, desc);
+ loadedRef.set(idx.ref);
+ return new DfsBlockCache.Ref<>(idxKey, REF_POSITION,
+ idx.size, idx.ref);
+ });
+ if (loadedRef.get() == null) {
+ ctx.stats.idxCacheHit++;
+ }
+ return cachedRef.get() != null ? cachedRef.get() : loadedRef.get();
+ }
+
+ private static RefWithSize<PackIndex> loadPackIndex(DfsReader ctx,
+ DfsPackDescription desc) throws IOException {
+ try {
+ ctx.stats.readIdx++;
+ long start = System.nanoTime();
+ try (ReadableChannel rc = ctx.db.openFile(desc, INDEX)) {
+ PackIndex idx = PackIndex.read(alignTo8kBlocks(rc));
+ ctx.stats.readIdxBytes += rc.position();
+ return new RefWithSize<>(idx,
+ idx.getObjectCount() * REC_SIZE);
+ } finally {
+ ctx.stats.readIdxMicros += elapsedMicros(start);
+ }
+ } catch (EOFException e) {
+ throw new IOException(
+ MessageFormat.format(DfsText.get().shortReadOfIndex,
+ desc.getFileName(INDEX)),
+ e);
+ } catch (IOException e) {
+ throw new IOException(
+ MessageFormat.format(DfsText.get().cannotReadIndex,
+ desc.getFileName(INDEX)),
+ e);
+ }
+ }
+
+ @Override
+ public PackReverseIndex reverseIndex(DfsReader ctx, PackIndex idx)
+ throws IOException {
+ DfsStreamKey revKey = desc.getStreamKey(REVERSE_INDEX);
+ // Keep the value parsed in the loader, in case the Ref<> is
+ // nullified in ClockBlockCacheTable#reserveSpace
+ // before we read its value.
+ AtomicReference<PackReverseIndex> loadedRef = new AtomicReference<>(
+ null);
+ DfsBlockCache.Ref<PackReverseIndex> cachedRef = cache
+ .getOrLoadRef(revKey, REF_POSITION, () -> {
+ RefWithSize<PackReverseIndex> ridx = loadReverseIdx(ctx,
+ idx);
+ loadedRef.set(ridx.ref);
+ return new DfsBlockCache.Ref<>(revKey, REF_POSITION,
+ ridx.size, ridx.ref);
+ });
+ if (loadedRef.get() == null) {
+ ctx.stats.ridxCacheHit++;
+ }
+ return cachedRef.get() != null ? cachedRef.get() : loadedRef.get();
+ }
+
+ private static RefWithSize<PackReverseIndex> loadReverseIdx(
+ DfsReader ctx, PackIndex idx) {
+ ctx.stats.readReverseIdx++;
+ long start = System.nanoTime();
+ PackReverseIndex revidx = PackReverseIndexFactory
+ .computeFromIndex(idx);
+ ctx.stats.readReverseIdxMicros += elapsedMicros(start);
+ return new RefWithSize<>(revidx, idx.getObjectCount() * 8);
+ }
+ }
+
+ private static final class RefWithSize<V> {
+ final V ref;
+ final long size;
+ RefWithSize(V ref, long size) {
+ this.ref = ref;
+ this.size = size;
+ }
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java
index d8e191c4e0..b19f65c69b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackParser.java
@@ -101,7 +101,6 @@ public class DfsPackParser extends PackParser {
this.packDigest = Constants.newMessageDigest();
}
- /** {@inheritDoc} */
@Override
public PackLock parse(ProgressMonitor receiving, ProgressMonitor resolving)
throws IOException {
@@ -123,10 +122,11 @@ public class DfsPackParser extends PackParser {
packDsc.setBlockSize(PACK, blockSize);
writePackIndex();
+ objins.writeObjectSizeIndex(packDsc, getSortedObjectList(null));
objdb.commitPack(Collections.singletonList(packDsc), null);
rollback = false;
- DfsPackFile p = new DfsPackFile(blockCache, packDsc);
+ DfsPackFile p = objdb.createDfsPackFile(blockCache, packDsc);
p.setBlockSize(blockSize);
if (packIndex != null)
p.setPackIndex(packIndex);
@@ -172,7 +172,6 @@ public class DfsPackParser extends PackParser {
return packDsc;
}
- /** {@inheritDoc} */
@Override
protected void onPackHeader(long objectCount) throws IOException {
if (objectCount == 0) {
@@ -194,34 +193,29 @@ public class DfsPackParser extends PackParser {
currBuf = new byte[blockSize];
}
- /** {@inheritDoc} */
@Override
protected void onBeginWholeObject(long streamPosition, int type,
long inflatedSize) throws IOException {
crc.reset();
}
- /** {@inheritDoc} */
@Override
protected void onEndWholeObject(PackedObjectInfo info) throws IOException {
info.setCRC((int) crc.getValue());
}
- /** {@inheritDoc} */
@Override
protected void onBeginOfsDelta(long streamPosition,
long baseStreamPosition, long inflatedSize) throws IOException {
crc.reset();
}
- /** {@inheritDoc} */
@Override
protected void onBeginRefDelta(long streamPosition, AnyObjectId baseId,
long inflatedSize) throws IOException {
crc.reset();
}
- /** {@inheritDoc} */
@Override
protected UnresolvedDelta onEndDelta() throws IOException {
UnresolvedDelta delta = new UnresolvedDelta();
@@ -229,28 +223,24 @@ public class DfsPackParser extends PackParser {
return delta;
}
- /** {@inheritDoc} */
@Override
protected void onInflatedObjectData(PackedObjectInfo obj, int typeCode,
byte[] data) throws IOException {
// DfsPackParser ignores this event.
}
- /** {@inheritDoc} */
@Override
protected void onObjectHeader(Source src, byte[] raw, int pos, int len)
throws IOException {
crc.update(raw, pos, len);
}
- /** {@inheritDoc} */
@Override
protected void onObjectData(Source src, byte[] raw, int pos, int len)
throws IOException {
crc.update(raw, pos, len);
}
- /** {@inheritDoc} */
@Override
protected void onStoreStream(byte[] raw, int pos, int len)
throws IOException {
@@ -297,7 +287,6 @@ public class DfsPackParser extends PackParser {
return v;
}
- /** {@inheritDoc} */
@Override
protected void onPackFooter(byte[] hash) throws IOException {
// The base class will validate the original hash matches
@@ -307,7 +296,6 @@ public class DfsPackParser extends PackParser {
packHash = hash;
}
- /** {@inheritDoc} */
@Override
protected ObjectTypeAndSize seekDatabase(PackedObjectInfo obj,
ObjectTypeAndSize info) throws IOException {
@@ -316,7 +304,6 @@ public class DfsPackParser extends PackParser {
return readObjectHeader(info);
}
- /** {@inheritDoc} */
@Override
protected ObjectTypeAndSize seekDatabase(UnresolvedDelta delta,
ObjectTypeAndSize info) throws IOException {
@@ -325,7 +312,6 @@ public class DfsPackParser extends PackParser {
return readObjectHeader(info);
}
- /** {@inheritDoc} */
@Override
protected int readDatabase(byte[] dst, int pos, int cnt) throws IOException {
if (cnt == 0)
@@ -381,13 +367,11 @@ public class DfsPackParser extends PackParser {
return (pos / blockSize) * blockSize;
}
- /** {@inheritDoc} */
@Override
protected boolean checkCRC(int oldCRC) {
return oldCRC == (int) crc.getValue();
}
- /** {@inheritDoc} */
@Override
protected boolean onAppendBase(final int typeCode, final byte[] data,
final PackedObjectInfo info) throws IOException {
@@ -427,7 +411,6 @@ public class DfsPackParser extends PackParser {
return true;
}
- /** {@inheritDoc} */
@Override
protected void onEndThinPack() throws IOException {
// Normally when a thin pack is closed the pack header gets
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPacksChangedEvent.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPacksChangedEvent.java
index 750c118268..f4e863d02d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPacksChangedEvent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPacksChangedEvent.java
@@ -18,13 +18,11 @@ import org.eclipse.jgit.events.RepositoryEvent;
*/
public class DfsPacksChangedEvent
extends RepositoryEvent<DfsPacksChangedListener> {
- /** {@inheritDoc} */
@Override
public Class<DfsPacksChangedListener> getListenerType() {
return DfsPacksChangedListener.class;
}
- /** {@inheritDoc} */
@Override
public void dispatch(DfsPacksChangedListener listener) {
listener.onPacksChanged(this);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
index 8d8a766b0f..f50cd597e5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java
@@ -21,26 +21,27 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
+import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.commitgraph.CommitGraph;
import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackList;
+import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
+import org.eclipse.jgit.internal.storage.dfs.DfsReader.PackLoadListener.DfsBlockData;
import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl;
import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
-import org.eclipse.jgit.internal.storage.file.PackIndex;
-import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
import org.eclipse.jgit.internal.storage.pack.CachedPack;
import org.eclipse.jgit.internal.storage.pack.ObjectReuseAsIs;
import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
+import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
import org.eclipse.jgit.internal.storage.pack.PackWriter;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
@@ -49,12 +50,12 @@ import org.eclipse.jgit.lib.AsyncObjectLoaderQueue;
import org.eclipse.jgit.lib.AsyncObjectSizeQueue;
import org.eclipse.jgit.lib.BitmapIndex;
import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.InflaterCache;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.ProgressMonitor;
-import org.eclipse.jgit.util.BlockList;
/**
* Reader to access repository content through.
@@ -78,6 +79,7 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
private DeltaBaseCache baseCache;
private DfsPackFile last;
private boolean avoidUnreachable;
+ private List<PackLoadListener> packLoadListeners = new ArrayList<>();
/**
* Initialize a new DfsReader
@@ -100,30 +102,38 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
return baseCache;
}
- /** {@inheritDoc} */
@Override
public ObjectReader newReader() {
return db.newReader();
}
- /** {@inheritDoc} */
@Override
public void setAvoidUnreachableObjects(boolean avoid) {
avoidUnreachable = avoid;
}
- /** {@inheritDoc} */
@Override
public BitmapIndex getBitmapIndex() throws IOException {
for (DfsPackFile pack : db.getPacks()) {
PackBitmapIndex bitmapIndex = pack.getBitmapIndex(this);
if (bitmapIndex != null)
- return new BitmapIndexImpl(bitmapIndex);
+ return createBitmapIndex(bitmapIndex);
}
return null;
}
- /** {@inheritDoc} */
+ /**
+ * Give subclasses a chance to record pack index stats
+ *
+ * @param packBitmapIndex
+ * packBitmapIndex found in a pack (never null)
+ * @return an instance of BitmapIndex
+ */
+ protected BitmapIndex createBitmapIndex(
+ @NonNull PackBitmapIndex packBitmapIndex) {
+ return new BitmapIndexImpl(packBitmapIndex);
+ }
+
@Override
public Optional<CommitGraph> getCommitGraph() throws IOException {
for (DfsPackFile pack : db.getPacks()) {
@@ -135,7 +145,6 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
return Optional.empty();
}
- /** {@inheritDoc} */
@Override
public Collection<CachedPack> getCachedPacksAndUpdate(
BitmapBuilder needBitmap) throws IOException {
@@ -148,7 +157,6 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
return Collections.emptyList();
}
- /** {@inheritDoc} */
@Override
public Collection<ObjectId> resolve(AbbreviatedObjectId id)
throws IOException {
@@ -177,37 +185,48 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
}
}
- /** {@inheritDoc} */
@Override
public boolean has(AnyObjectId objectId) throws IOException {
+ return findPack(objectId) >= 0;
+ }
+
+ private int findPack(AnyObjectId objectId) throws IOException {
if (last != null
- && !skipGarbagePack(last)
- && last.hasObject(this, objectId))
- return true;
+ && !skipGarbagePack(last)) {
+ int idxPos = last.findIdxPosition(this, objectId);
+ if (idxPos >= 0) {
+ return idxPos;
+ }
+ }
+
PackList packList = db.getPackList();
- if (hasImpl(packList, objectId)) {
- return true;
+ int idxPos = findInPackList(packList, objectId);
+ if (idxPos >= 0) {
+ return idxPos;
} else if (packList.dirty()) {
stats.scanPacks++;
- return hasImpl(db.scanPacks(packList), objectId);
+ idxPos = findInPackList(db.scanPacks(packList), objectId);
+ return idxPos;
}
- return false;
+ return -1;
}
- private boolean hasImpl(PackList packList, AnyObjectId objectId)
+ // Leave "last" pointing to the pack and return the idx position of the
+ // object (-1 if not found)
+ private int findInPackList(PackList packList, AnyObjectId objectId)
throws IOException {
for (DfsPackFile pack : packList.packs) {
if (pack == last || skipGarbagePack(pack))
continue;
- if (pack.hasObject(this, objectId)) {
+ int idxPos = pack.findIdxPosition(this, objectId);
+ if (idxPos >= 0) {
last = pack;
- return true;
+ return idxPos;
}
}
- return false;
+ return -1;
}
- /** {@inheritDoc} */
@Override
public ObjectLoader open(AnyObjectId objectId, int typeHint)
throws MissingObjectException, IncorrectObjectTypeException,
@@ -262,7 +281,6 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
return null;
}
- /** {@inheritDoc} */
@Override
public Set<ObjectId> getShallowCommits() {
return Collections.emptySet();
@@ -299,7 +317,7 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
private <T extends ObjectId> Iterable<FoundObject<T>> findAll(
Iterable<T> objectIds) throws IOException {
- Collection<T> pending = new LinkedList<>();
+ HashSet<T> pending = new HashSet<>();
for (T id : objectIds) {
pending.add(id);
}
@@ -319,22 +337,21 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
}
private <T extends ObjectId> void findAllImpl(PackList packList,
- Collection<T> pending, List<FoundObject<T>> r) {
+ HashSet<T> pending, List<FoundObject<T>> r) {
DfsPackFile[] packs = packList.packs;
if (packs.length == 0) {
return;
}
int lastIdx = 0;
DfsPackFile lastPack = packs[lastIdx];
-
- OBJECT_SCAN: for (Iterator<T> it = pending.iterator(); it.hasNext();) {
- T t = it.next();
+ HashSet<T> toRemove = new HashSet<>();
+ OBJECT_SCAN: for (T t : pending) {
if (!skipGarbagePack(lastPack)) {
try {
long p = lastPack.findOffset(this, t);
if (0 < p) {
r.add(new FoundObject<>(t, lastIdx, lastPack, p));
- it.remove();
+ toRemove.add(t);
continue;
}
} catch (IOException e) {
@@ -352,7 +369,7 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
long p = pack.findOffset(this, t);
if (0 < p) {
r.add(new FoundObject<>(t, i, pack, p));
- it.remove();
+ toRemove.add(t);
lastIdx = i;
lastPack = pack;
continue OBJECT_SCAN;
@@ -362,6 +379,7 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
}
}
}
+ pending.removeAll(toRemove);
last = lastPack;
}
@@ -370,7 +388,6 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
return avoidUnreachable && pack.isGarbage();
}
- /** {@inheritDoc} */
@Override
public <T extends ObjectId> AsyncObjectLoaderQueue<T> open(
Iterable<T> objectIds, final boolean reportMissing) {
@@ -430,7 +447,6 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
};
}
- /** {@inheritDoc} */
@Override
public <T extends ObjectId> AsyncObjectSizeQueue<T> getObjectSize(
Iterable<T> objectIds, final boolean reportMissing) {
@@ -492,62 +508,102 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
};
}
- /** {@inheritDoc} */
@Override
public long getObjectSize(AnyObjectId objectId, int typeHint)
throws MissingObjectException, IncorrectObjectTypeException,
IOException {
- if (last != null && !skipGarbagePack(last)) {
- long sz = last.getObjectSize(this, objectId);
- if (0 <= sz) {
- return sz;
+ int idxPos = findPack(objectId);
+ if (idxPos < 0) {
+ if (typeHint == OBJ_ANY) {
+ throw new MissingObjectException(objectId.copy(),
+ JGitText.get().unknownObjectType2);
}
+ throw new MissingObjectException(objectId.copy(), typeHint);
}
- PackList packList = db.getPackList();
- long sz = getObjectSizeImpl(packList, objectId);
- if (0 <= sz) {
+ if (typeHint != Constants.OBJ_BLOB || !safeHasObjectSizeIndex(last)) {
+ return last.getObjectSize(this, objectId);
+ }
+
+ long sz = safeGetIndexedObjectSize(last, idxPos);
+ if (sz >= 0) {
return sz;
}
- if (packList.dirty()) {
- sz = getObjectSizeImpl(packList, objectId);
- if (0 <= sz) {
- return sz;
+ return last.getObjectSize(this, objectId);
+ }
+
+
+ @Override
+ public boolean isNotLargerThan(AnyObjectId objectId, int typeHint,
+ long limit) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ int idxPos = findPack(objectId);
+ if (idxPos < 0) {
+ if (typeHint == OBJ_ANY) {
+ throw new MissingObjectException(objectId.copy(),
+ JGitText.get().unknownObjectType2);
}
+ throw new MissingObjectException(objectId.copy(), typeHint);
}
- if (typeHint == OBJ_ANY) {
- throw new MissingObjectException(objectId.copy(),
- JGitText.get().unknownObjectType2);
+ stats.isNotLargerThanCallCount += 1;
+ if (typeHint != Constants.OBJ_BLOB || !safeHasObjectSizeIndex(last)) {
+ return last.getObjectSize(this, objectId) <= limit;
}
- throw new MissingObjectException(objectId.copy(), typeHint);
+
+ long sz = safeGetIndexedObjectSize(last, idxPos);
+ if (sz >= 0) {
+ return sz <= limit;
+ }
+
+ if (isLimitInsideIndexThreshold(last, limit)) {
+ // With threshold T, not-found means object < T
+ // If limit L > T, then object < T < L
+ return true;
+ }
+
+ return last.getObjectSize(this, objectId) <= limit;
}
- private long getObjectSizeImpl(PackList packList, AnyObjectId objectId)
- throws IOException {
- for (DfsPackFile pack : packList.packs) {
- if (pack == last || skipGarbagePack(pack)) {
- continue;
- }
- long sz = pack.getObjectSize(this, objectId);
- if (0 <= sz) {
- last = pack;
- return sz;
- }
+ private boolean safeHasObjectSizeIndex(DfsPackFile pack) {
+ try {
+ return pack.hasObjectSizeIndex(this);
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ private long safeGetIndexedObjectSize(DfsPackFile pack,
+ int idxPos) {
+ long sz;
+ try {
+ sz = pack.getIndexedObjectSize(this, idxPos);
+ } catch (IOException e) {
+ // If there is any error in the index, we should have seen it
+ // on hasObjectSizeIndex.
+ throw new IllegalStateException(e);
+ }
+ if (sz < 0) {
+ stats.objectSizeIndexMiss += 1;
+ } else {
+ stats.objectSizeIndexHit += 1;
+ }
+ return sz;
+ }
+
+ private boolean isLimitInsideIndexThreshold(DfsPackFile pack, long limit) {
+ try {
+ return pack.getObjectSizeIndexThreshold(this) <= limit;
+ } catch (IOException e) {
+ return false;
}
- return -1;
}
- /** {@inheritDoc} */
@Override
public DfsObjectToPack newObjectToPack(AnyObjectId objectId, int type) {
return new DfsObjectToPack(objectId, type);
}
- private static final Comparator<DfsObjectToPack> OFFSET_SORT = (
- DfsObjectToPack a,
- DfsObjectToPack b) -> Long.signum(a.getOffset() - b.getOffset());
-
@Override
public void selectObjectRepresentation(PackWriter packer,
ProgressMonitor monitor, Iterable<ObjectToPack> objects)
@@ -567,16 +623,15 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
ProgressMonitor monitor, Iterable<ObjectToPack> objects,
List<DfsPackFile> packs, boolean skipFound) throws IOException {
for (DfsPackFile pack : packs) {
- List<DfsObjectToPack> tmp = findAllFromPack(pack, objects, skipFound);
- if (tmp.isEmpty())
+ List<DfsObjectToPack> inPack = pack.findAllFromPack(this, objects, skipFound);
+ if (inPack.isEmpty())
continue;
- Collections.sort(tmp, OFFSET_SORT);
- PackReverseIndex rev = pack.getReverseIdx(this);
DfsObjectRepresentation rep = new DfsObjectRepresentation(pack);
- for (DfsObjectToPack otp : tmp) {
- pack.representation(rep, otp.getOffset(), this, rev);
+ for (DfsObjectToPack otp : inPack) {
+ // Populate rep.{offset,length} from the pack
+ pack.fillRepresentation(rep, otp.getOffset(), this);
otp.setOffset(0);
- packer.select(otp, rep);
+ packer.select(otp, rep); // Set otp.offset from rep
if (!otp.isFound()) {
otp.setFound();
monitor.update(1);
@@ -623,26 +678,8 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
return false;
}
- private List<DfsObjectToPack> findAllFromPack(DfsPackFile pack,
- Iterable<ObjectToPack> objects, boolean skipFound)
- throws IOException {
- List<DfsObjectToPack> tmp = new BlockList<>();
- PackIndex idx = pack.getPackIndex(this);
- for (ObjectToPack obj : objects) {
- DfsObjectToPack otp = (DfsObjectToPack) obj;
- if (skipFound && otp.isFound()) {
- continue;
- }
- long p = idx.findOffset(otp);
- if (0 < p && !pack.isCorrupt(p)) {
- otp.setOffset(p);
- tmp.add(otp);
- }
- }
- return tmp;
- }
- /** {@inheritDoc} */
+
@Override
public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp,
boolean validate) throws IOException,
@@ -651,7 +688,6 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
src.pack.copyAsIs(out, src, validate, this);
}
- /** {@inheritDoc} */
@Override
public void writeObjects(PackOutputStream out, List<ObjectToPack> list)
throws IOException {
@@ -659,7 +695,6 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
out.writeObject(otp);
}
- /** {@inheritDoc} */
@Override
public void copyPackAsIs(PackOutputStream out, CachedPack pack)
throws IOException {
@@ -796,6 +831,100 @@ public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
return new DfsReaderIoStats(stats);
}
+ /** Announces when data is loaded by reader */
+ protected interface PackLoadListener {
+ /**
+ * Immutable copy of a DFS block metadata
+ */
+ class DfsBlockData {
+ private final int identityHash;
+ private final int size;
+
+ static DfsBlockData of(DfsBlock src) {
+ return new DfsBlockData(src);
+ }
+
+ private DfsBlockData(DfsBlock src) {
+ this.identityHash = System.identityHashCode(src);
+ this.size = src.size();
+ }
+
+ public int getIdentityHash() {
+ return identityHash;
+ }
+
+ public int getSize() {
+ return size;
+ }
+ }
+
+ /**
+ * This is called when an index reference (e.g. primary index, reverse
+ * index, ...) is set in the reader, regarless if loaded from scratch or
+ * copied from cache.
+ *
+ * During the lifetime of the reader, the reference for an index should
+ * be set only once.
+ *
+ * @param packName
+ * Name of the pack
+ * @param src
+ * Source of the pack (e.g. GC, COMPACT, ...)
+ * @param ext
+ * Extension in the pack (e.g. IDX, RIDX, ...)
+ * @param size
+ * Size of the data loaded (usually as bytes in disk)
+ * @param loadedIdx
+ * reference to the loaded index
+ */
+ void onIndexLoad(String packName, PackSource src, PackExt ext, long size,
+ Object loadedIdx);
+
+ /**
+ * This is called when a dfs block is loaded into the reader.
+ *
+ * The reader keeps only one block at a time in memory, so during a
+ * request the same block could be loaded multiple times.
+ *
+ * @param packName
+ * Name of the pack this block belongs to
+ * @param src
+ * Source of the pack (e.g. GC, COMPACT, ...)
+ * @param ext
+ * Extension in the pack (e.g. PACK or REFTABLE)
+ * @param position
+ * Offset in the file requested by caller
+ * @param dfsBlockData
+ * Metadata of the block
+ */
+ void onBlockLoad(String packName, PackSource src, PackExt ext,
+ long position, DfsBlockData dfsBlockData);
+ }
+
+ void emitIndexLoad(DfsPackDescription packDescription, PackExt ext,
+ Object loadedIdx) {
+ packLoadListeners.forEach(
+ listener -> listener.onIndexLoad(packDescription.getFileName(ext),
+ packDescription.getPackSource(), ext,
+ packDescription.getFileSize(ext), loadedIdx));
+ }
+
+ void emitBlockLoad(BlockBasedFile file, long position, DfsBlock dfsBlock) {
+ packLoadListeners
+ .forEach(listener -> listener.onBlockLoad(file.getFileName(),
+ file.desc.getPackSource(), file.ext, position,
+ DfsBlockData.of(dfsBlock)));
+ }
+
+ /**
+ * Add listener to record loads by this reader
+ *
+ * @param listener a listener
+ */
+ protected void addPackLoadListener(PackLoadListener listener) {
+ packLoadListeners.add(listener);
+ }
+
/**
* {@inheritDoc}
* <p>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java
index 5ac7985e97..fcfa3e0dee 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderIoStats.java
@@ -10,12 +10,15 @@
package org.eclipse.jgit.internal.storage.dfs;
+import org.eclipse.jgit.lib.AnyObjectId;
+
/**
* IO statistics for a {@link org.eclipse.jgit.internal.storage.dfs.DfsReader}.
*/
public class DfsReaderIoStats {
/** POJO to accumulate IO statistics. */
public static class Accumulator {
+
/** Number of times the reader explicitly called scanPacks. */
long scanPacks;
@@ -31,38 +34,50 @@ public class DfsReaderIoStats {
/** Total number of cache hits for commit graphs. */
long commitGraphCacheHit;
+ /** Total number of cache hits for object size indexes. */
+ long objectSizeIndexCacheHit;
+
/** Total number of complete pack indexes read into memory. */
long readIdx;
- /** Total number of complete bitmap indexes read into memory. */
- long readBitmap;
-
/** Total number of reverse indexes added into memory. */
long readReverseIdx;
+ /** Total number of complete bitmap indexes read into memory. */
+ long readBitmap;
+
/** Total number of complete commit graphs read into memory. */
long readCommitGraph;
+ /** Total number of object size indexes added into memory. */
+ long readObjectSizeIndex;
+
/** Total number of bytes read from pack indexes. */
long readIdxBytes;
+ /** Total number of bytes read from bitmap indexes. */
+ long readBitmapIdxBytes;
+
/** Total number of bytes read from commit graphs. */
long readCommitGraphBytes;
+ /** Total numer of bytes read from object size index */
+ long readObjectSizeIndexBytes;
+
/** Total microseconds spent reading pack indexes. */
long readIdxMicros;
/** Total microseconds spent creating reverse indexes. */
long readReverseIdxMicros;
+ /** Total microseconds spent reading bitmap indexes. */
+ long readBitmapIdxMicros;
+
/** Total microseconds spent creating commit graphs. */
long readCommitGraphMicros;
- /** Total number of bytes read from bitmap indexes. */
- long readBitmapIdxBytes;
-
- /** Total microseconds spent reading bitmap indexes. */
- long readBitmapIdxMicros;
+ /** Total microseconds spent creating object size indexes */
+ long readObjectSizeIndexMicros;
/** Total number of block cache hits. */
long blockCacheHit;
@@ -88,6 +103,15 @@ public class DfsReaderIoStats {
/** Total microseconds spent inflating compressed bytes. */
long inflationMicros;
+ /** Count of queries for the size of an object via #isNotLargerThan */
+ long isNotLargerThanCallCount;
+
+ /** Object was below threshold in the object size index */
+ long objectSizeIndexMiss;
+
+ /** Object size found in the object size index */
+ long objectSizeIndexHit;
+
Accumulator() {
}
}
@@ -144,6 +168,15 @@ public class DfsReaderIoStats {
}
/**
+ * Get total number of object size index cache hits.
+ *
+ * @return total number of object size index cache hits.
+ */
+ public long getObjectSizeIndexCacheHits() {
+ return stats.objectSizeIndexCacheHit;
+ }
+
+ /**
* Get total number of complete pack indexes read into memory.
*
* @return total number of complete pack indexes read into memory.
@@ -162,6 +195,15 @@ public class DfsReaderIoStats {
}
/**
+ * Get total number of complete bitmap indexes read into memory.
+ *
+ * @return total number of complete bitmap indexes read into memory.
+ */
+ public long getReadBitmapIndexCount() {
+ return stats.readBitmap;
+ }
+
+ /**
* Get total number of times the commit graph read into memory.
*
* @return total number of commit graph read into memory.
@@ -171,12 +213,12 @@ public class DfsReaderIoStats {
}
/**
- * Get total number of complete bitmap indexes read into memory.
+ * Get total number of complete object size indexes read into memory.
*
- * @return total number of complete bitmap indexes read into memory.
+ * @return total number of complete object size indexes read into memory.
*/
- public long getReadBitmapIndexCount() {
- return stats.readBitmap;
+ public long getReadObjectSizeIndexCount() {
+ return stats.readObjectSizeIndex;
}
/**
@@ -189,6 +231,15 @@ public class DfsReaderIoStats {
}
/**
+ * Get total number of bytes read from bitmap indexes.
+ *
+ * @return total number of bytes read from bitmap indexes.
+ */
+ public long getReadBitmapIndexBytes() {
+ return stats.readBitmapIdxBytes;
+ }
+
+ /**
* Get total number of bytes read from commit graphs.
*
* @return total number of bytes read from commit graphs.
@@ -198,6 +249,15 @@ public class DfsReaderIoStats {
}
/**
+ * Get total number of bytes read from object size indexes.
+ *
+ * @return total number of bytes read from object size indexes.
+ */
+ public long getObjectSizeIndexBytes() {
+ return stats.readObjectSizeIndexBytes;
+ }
+
+ /**
* Get total microseconds spent reading pack indexes.
*
* @return total microseconds spent reading pack indexes.
@@ -216,30 +276,30 @@ public class DfsReaderIoStats {
}
/**
- * Get total microseconds spent reading commit graphs.
+ * Get total microseconds spent reading bitmap indexes.
*
- * @return total microseconds spent reading commit graphs.
+ * @return total microseconds spent reading bitmap indexes.
*/
- public long getReadCommitGraphMicros() {
- return stats.readCommitGraphMicros;
+ public long getReadBitmapIndexMicros() {
+ return stats.readBitmapIdxMicros;
}
/**
- * Get total number of bytes read from bitmap indexes.
+ * Get total microseconds spent reading commit graphs.
*
- * @return total number of bytes read from bitmap indexes.
+ * @return total microseconds spent reading commit graphs.
*/
- public long getReadBitmapIndexBytes() {
- return stats.readBitmapIdxBytes;
+ public long getReadCommitGraphMicros() {
+ return stats.readCommitGraphMicros;
}
/**
- * Get total microseconds spent reading bitmap indexes.
+ * Get total microseconds spent reading object size indexes.
*
- * @return total microseconds spent reading bitmap indexes.
+ * @return total microseconds spent reading object size indexes.
*/
- public long getReadBitmapIndexMicros() {
- return stats.readBitmapIdxMicros;
+ public long getReadObjectSizeIndexMicros() {
+ return stats.readObjectSizeIndexMicros;
}
/**
@@ -297,4 +357,41 @@ public class DfsReaderIoStats {
public long getInflationMicros() {
return stats.inflationMicros;
}
+
+ /**
+ * Get count of invocations to
+ * {@link DfsReader#isNotLargerThan(AnyObjectId, int, long)}
+ * <p>
+ * Each call could use the object-size index or not.
+ *
+ * @return how many times the size of an object was checked with
+ * {@link DfsReader#isNotLargerThan(AnyObjectId, int, long)}
+ */
+ public long getIsNotLargerThanCallCount() {
+ return stats.isNotLargerThanCallCount;
+ }
+
+ /**
+ * Get number of times the size of a blob was found in the object size
+ * index.
+ * <p>
+ * This counts only queries for blobs on packs with object size index.
+ *
+ * @return count of object size index hits
+ */
+ public long getObjectSizeIndexHits() {
+ return stats.objectSizeIndexHit;
+ }
+
+ /**
+ * Get number of times the size of an object was not found in the object
+ * size index. This usually means it was below the threshold.
+ * <p>
+ * This counts only queries for blobs on packs with object size index.
+ *
+ * @return count of object size index misses.
+ */
+ public long getObjectSizeIndexMisses() {
+ return stats.objectSizeIndexMiss;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
index 146f76167d..c3974691a1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java
@@ -13,8 +13,10 @@ package org.eclipse.jgit.internal.storage.dfs;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DFS_SECTION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_DELTA_BASE_CACHE_LIMIT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_LOAD_REV_INDEX_IN_PARALLEL;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_BUFFER;
-import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_FILE_TRESHOLD;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_FILE_THRESHOLD;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_USE_OBJECT_SIZE_INDEX;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.storage.pack.PackConfig;
@@ -36,6 +38,8 @@ public class DfsReaderOptions {
private boolean loadRevIndexInParallel;
+ private boolean useObjectSizeIndex;
+
/**
* Create a default reader configuration.
*/
@@ -137,6 +141,28 @@ public class DfsReaderOptions {
}
/**
+ * Use the object size index if available.
+ *
+ * @return true if the reader should try to use the object size index. if
+ * false, the reader ignores that index.
+ */
+ public boolean shouldUseObjectSizeIndex() {
+ return useObjectSizeIndex;
+ }
+
+ /**
+ * Set if the reader should try to use the object size index
+ *
+ * @param useObjectSizeIndex true to use it, false to ignore the object size index
+ *
+ * @return {@code this}
+ */
+ public DfsReaderOptions setUseObjectSizeIndex(boolean useObjectSizeIndex) {
+ this.useObjectSizeIndex = useObjectSizeIndex;
+ return this;
+ }
+
+ /**
* Update properties by setting fields from the configuration.
* <p>
* If a property is not defined in the configuration, then it is left
@@ -157,7 +183,7 @@ public class DfsReaderOptions {
long sft = rc.getLong(
CONFIG_CORE_SECTION,
CONFIG_DFS_SECTION,
- CONFIG_KEY_STREAM_FILE_TRESHOLD,
+ CONFIG_KEY_STREAM_FILE_THRESHOLD,
getStreamFileThreshold());
sft = Math.min(sft, maxMem / 4); // don't use more than 1/4 of the heap
sft = Math.min(sft, Integer.MAX_VALUE); // cannot exceed array length
@@ -168,6 +194,13 @@ public class DfsReaderOptions {
CONFIG_DFS_SECTION,
CONFIG_KEY_STREAM_BUFFER,
getStreamPackBufferSize()));
+
+ setUseObjectSizeIndex(rc.getBoolean(CONFIG_CORE_SECTION,
+ CONFIG_DFS_SECTION, CONFIG_KEY_USE_OBJECT_SIZE_INDEX,
+ false));
+ setLoadRevIndexInParallel(
+ rc.getBoolean(CONFIG_CORE_SECTION, CONFIG_DFS_SECTION,
+ CONFIG_KEY_LOAD_REV_INDEX_IN_PARALLEL, false));
return this;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java
index ae656707ac..4547bcab5d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java
@@ -34,7 +34,6 @@ import org.eclipse.jgit.util.RefMap;
/**
* Abstract DfsRefDatabase class.
- *
*/
public abstract class DfsRefDatabase extends RefDatabase {
private final DfsRepository repository;
@@ -65,7 +64,6 @@ public abstract class DfsRefDatabase extends RefDatabase {
return 0 < read().size();
}
- /** {@inheritDoc} */
@Override
public Ref exactRef(String name) throws IOException {
RefCache curr = read();
@@ -73,13 +71,11 @@ public abstract class DfsRefDatabase extends RefDatabase {
return ref != null ? resolve(ref, 0, curr.ids) : null;
}
- /** {@inheritDoc} */
@Override
public List<Ref> getAdditionalRefs() {
return Collections.emptyList();
}
- /** {@inheritDoc} */
@Override
public Map<String, Ref> getRefs(String prefix) throws IOException {
RefCache curr = read();
@@ -126,7 +122,6 @@ public abstract class DfsRefDatabase extends RefDatabase {
return new SymbolicRef(ref.getName(), dst);
}
- /** {@inheritDoc} */
@Override
public Ref peel(Ref ref) throws IOException {
final Ref oldLeaf = ref.getLeaf();
@@ -176,7 +171,6 @@ public abstract class DfsRefDatabase extends RefDatabase {
return leaf;
}
- /** {@inheritDoc} */
@Override
public RefUpdate newUpdate(String refName, boolean detach)
throws IOException {
@@ -193,7 +187,6 @@ public abstract class DfsRefDatabase extends RefDatabase {
return update;
}
- /** {@inheritDoc} */
@Override
public RefRename newRename(String fromName, String toName)
throws IOException {
@@ -202,7 +195,6 @@ public abstract class DfsRefDatabase extends RefDatabase {
return new DfsRefRename(src, dst);
}
- /** {@inheritDoc} */
@Override
public boolean isNameConflicting(String refName) throws IOException {
RefList<Ref> all = read().ids;
@@ -224,19 +216,16 @@ public abstract class DfsRefDatabase extends RefDatabase {
return false;
}
- /** {@inheritDoc} */
@Override
public void create() {
// Nothing to do.
}
- /** {@inheritDoc} */
@Override
public void refresh() {
clearCache();
}
- /** {@inheritDoc} */
@Override
public void close() {
clearCache();
@@ -369,7 +358,11 @@ public abstract class DfsRefDatabase extends RefDatabase {
this(ids, old.sym);
}
- /** @return number of references in this cache. */
+ /**
+ * Get number of references
+ *
+ * @return number of references in this cache.
+ */
public int size() {
return ids.size();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefRename.java
index 569803575d..e76b0bc142 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefRename.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefRename.java
@@ -22,7 +22,6 @@ final class DfsRefRename extends RefRename {
super(src, dst);
}
- /** {@inheritDoc} */
@Override
protected Result doRename() throws IOException {
// TODO Correctly handle renaming foo/bar to foo.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefUpdate.java
index 28331a08e6..f327034123 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefUpdate.java
@@ -33,19 +33,16 @@ final class DfsRefUpdate extends RefUpdate {
this.refdb = refdb;
}
- /** {@inheritDoc} */
@Override
protected DfsRefDatabase getRefDatabase() {
return refdb;
}
- /** {@inheritDoc} */
@Override
protected DfsRepository getRepository() {
return refdb.getRepository();
}
- /** {@inheritDoc} */
@Override
protected boolean tryLock(boolean deref) throws IOException {
dstRef = getRef();
@@ -60,13 +57,11 @@ final class DfsRefUpdate extends RefUpdate {
return true;
}
- /** {@inheritDoc} */
@Override
protected void unlock() {
// No state is held while "locked".
}
- /** {@inheritDoc} */
@Override
public Result update(RevWalk walk) throws IOException {
try {
@@ -77,7 +72,6 @@ final class DfsRefUpdate extends RefUpdate {
}
}
- /** {@inheritDoc} */
@Override
protected Result doUpdate(Result desiredResult) throws IOException {
ObjectIdRef newRef;
@@ -102,7 +96,6 @@ final class DfsRefUpdate extends RefUpdate {
return Result.LOCK_FAILURE;
}
- /** {@inheritDoc} */
@Override
protected Result doDelete(Result desiredResult) throws IOException {
if (getRefDatabase().compareAndRemove(dstRef)) {
@@ -112,7 +105,6 @@ final class DfsRefUpdate extends RefUpdate {
return Result.LOCK_FAILURE;
}
- /** {@inheritDoc} */
@Override
protected Result doLink(String target) throws IOException {
final SymbolicRef newRef = new SymbolicRef(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java
index 6c3b056efd..2751cd2969 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java
@@ -28,6 +28,7 @@ import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.util.RefList;
@@ -75,19 +76,16 @@ public class DfsReftableDatabase extends DfsRefDatabase {
stack = null;
}
- /** {@inheritDoc} */
@Override
public boolean hasVersioning() {
return true;
}
- /** {@inheritDoc} */
@Override
public boolean performsAtomicTransactions() {
return true;
}
- /** {@inheritDoc} */
@Override
public BatchRefUpdate newBatchUpdate() {
DfsObjDatabase odb = getRepository().getObjectDatabase();
@@ -151,13 +149,11 @@ public class DfsReftableDatabase extends DfsRefDatabase {
return reftableDatabase.isNameConflicting(refName, new TreeSet<>(), new HashSet<>());
}
- /** {@inheritDoc} */
@Override
public Ref exactRef(String name) throws IOException {
return reftableDatabase.exactRef(name);
}
- /** {@inheritDoc} */
@Override
public Map<String, Ref> getRefs(String prefix) throws IOException {
List<Ref> refs = reftableDatabase.getRefsByPrefix(prefix);
@@ -169,21 +165,23 @@ public class DfsReftableDatabase extends DfsRefDatabase {
RefList.emptyList());
}
- /** {@inheritDoc} */
@Override
public List<Ref> getRefsByPrefix(String prefix) throws IOException {
return reftableDatabase.getRefsByPrefix(prefix);
}
- /** {@inheritDoc} */
@Override
public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> excludes)
throws IOException {
return reftableDatabase.getRefsByPrefixWithExclusions(include, excludes);
}
- /** {@inheritDoc} */
+ @Override
+ public ReflogReader getReflogReader(Ref ref) throws IOException {
+ return reftableDatabase.getReflogReader(ref.getName());
+ }
+
@Override
public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException {
if (!getReftableConfig().isIndexObjects()) {
@@ -192,13 +190,11 @@ public class DfsReftableDatabase extends DfsRefDatabase {
return reftableDatabase.getTipsWithSha1(id);
}
- /** {@inheritDoc} */
@Override
public boolean hasFastTipsWithSha1() throws IOException {
return reftableDatabase.hasFastTipsWithSha1();
}
- /** {@inheritDoc} */
@Override
public Ref peel(Ref ref) throws IOException {
Ref oldLeaf = ref.getLeaf();
@@ -233,7 +229,6 @@ public class DfsReftableDatabase extends DfsRefDatabase {
}
}
- /** {@inheritDoc} */
@Override
protected boolean compareAndPut(Ref oldRef, @Nullable Ref newRef)
throws IOException {
@@ -254,13 +249,11 @@ public class DfsReftableDatabase extends DfsRefDatabase {
}
}
- /** {@inheritDoc} */
@Override
protected boolean compareAndRemove(Ref oldRef) throws IOException {
return compareAndPut(oldRef, null);
}
- /** {@inheritDoc} */
@Override
protected RefCache scanAllRefs() throws IOException {
throw new UnsupportedOperationException();
@@ -276,7 +269,6 @@ public class DfsReftableDatabase extends DfsRefDatabase {
// Unnecessary; DfsReftableBatchRefUpdate calls clearCache().
}
- /** {@inheritDoc} */
@Override
protected void cachePeeledState(Ref oldLeaf, Ref newLeaf) {
// Do not cache peeled state in reftable.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableStack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableStack.java
index 27b03299e4..97ba4d52eb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableStack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableStack.java
@@ -80,7 +80,6 @@ public class DfsReftableStack implements AutoCloseable {
return Collections.unmodifiableList(tables);
}
- /** {@inheritDoc} */
@Override
public void close() {
for (ReftableReader t : tables) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
index 32c0ccdf4f..218f1e9552 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
@@ -45,7 +45,6 @@ public abstract class DfsRepository extends Repository {
this.description = builder.getRepositoryDescription();
}
- /** {@inheritDoc} */
@Override
public abstract DfsObjDatabase getObjectDatabase();
@@ -72,7 +71,6 @@ public abstract class DfsRepository extends Repository {
return true;
}
- /** {@inheritDoc} */
@Override
public void create(boolean bare) throws IOException {
if (exists())
@@ -85,39 +83,33 @@ public abstract class DfsRepository extends Repository {
throw new IOException(result.name());
}
- /** {@inheritDoc} */
@Override
public StoredConfig getConfig() {
return config;
}
- /** {@inheritDoc} */
@Override
public String getIdentifier() {
return getDescription().getRepositoryName();
}
- /** {@inheritDoc} */
@Override
public void scanForRepoChanges() throws IOException {
getRefDatabase().refresh();
getObjectDatabase().clearCache();
}
- /** {@inheritDoc} */
@Override
public void notifyIndexChanged(boolean internal) {
// Do not send notifications.
// There is no index, as there is no working tree.
}
- /** {@inheritDoc} */
@Override
public ReflogReader getReflogReader(String refName) throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public AttributesNodeProvider createAttributesNodeProvider() {
// TODO Check if the implementation used in FileRepository can be used
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryBuilder.java
index 943d92684b..ab5f7fe75e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryBuilder.java
@@ -72,7 +72,6 @@ public abstract class DfsRepositoryBuilder<B extends DfsRepositoryBuilder, R ext
return self();
}
- /** {@inheritDoc} */
@Override
public B setup() throws IllegalArgumentException, IOException {
super.setup();
@@ -97,7 +96,6 @@ public abstract class DfsRepositoryBuilder<B extends DfsRepositoryBuilder, R ext
// We don't support local file IO and thus shouldn't permit these to set.
- /** {@inheritDoc} */
@Override
public B setGitDir(File gitDir) {
if (gitDir != null)
@@ -105,7 +103,6 @@ public abstract class DfsRepositoryBuilder<B extends DfsRepositoryBuilder, R ext
return self();
}
- /** {@inheritDoc} */
@Override
public B setObjectDirectory(File objectDirectory) {
if (objectDirectory != null)
@@ -113,14 +110,12 @@ public abstract class DfsRepositoryBuilder<B extends DfsRepositoryBuilder, R ext
return self();
}
- /** {@inheritDoc} */
@Override
public B addAlternateObjectDirectory(File other) {
throw new UnsupportedOperationException(
JGitText.get().unsupportedAlternates);
}
- /** {@inheritDoc} */
@Override
public B setWorkTree(File workTree) {
if (workTree != null)
@@ -128,7 +123,6 @@ public abstract class DfsRepositoryBuilder<B extends DfsRepositoryBuilder, R ext
return self();
}
- /** {@inheritDoc} */
@Override
public B setIndexFile(File indexFile) {
if (indexFile != null)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryDescription.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryDescription.java
index 069956b586..e949085819 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryDescription.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepositoryDescription.java
@@ -42,7 +42,6 @@ public class DfsRepositoryDescription {
return repositoryName;
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
if (getRepositoryName() != null)
@@ -50,7 +49,6 @@ public class DfsRepositoryDescription {
return System.identityHashCode(this);
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object b) {
if (b instanceof DfsRepositoryDescription){
@@ -61,7 +59,6 @@ public class DfsRepositoryDescription {
return false;
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsStreamKey.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsStreamKey.java
index f3f30914f6..8983c2d2eb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsStreamKey.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsStreamKey.java
@@ -56,17 +56,14 @@ public abstract class DfsStreamKey {
this.packExtPos = ext == null ? 0 : ext.getPosition();
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
return hash;
}
- /** {@inheritDoc} */
@Override
public abstract boolean equals(Object o);
- /** {@inheritDoc} */
@SuppressWarnings("boxing")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsText.java
index f36ec06d3f..671d93d32d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsText.java
@@ -16,6 +16,7 @@ import org.eclipse.jgit.nls.TranslationBundle;
/**
* Translation bundle for the DFS storage implementation.
*/
+@SuppressWarnings("MissingSummary")
public class DfsText extends TranslationBundle {
/**
* Get an instance of this translation bundle.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
index 583b8b3f6b..fe8dc17b3d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
@@ -78,13 +78,11 @@ public class InMemoryRepository extends DfsRepository {
return new MemRefDatabase();
}
- /** {@inheritDoc} */
@Override
public MemObjDatabase getObjectDatabase() {
return objdb;
}
- /** {@inheritDoc} */
@Override
public RefDatabase getRefDatabase() {
return refdb;
@@ -102,14 +100,12 @@ public class InMemoryRepository extends DfsRepository {
refdb.performsAtomicTransactions = atomic;
}
- /** {@inheritDoc} */
@Override
@Nullable
public String getGitwebDescription() {
return gitwebDescription;
}
- /** {@inheritDoc} */
@Override
public void setGitwebDescription(@Nullable String d) {
gitwebDescription = d;
@@ -126,6 +122,8 @@ public class InMemoryRepository extends DfsRepository {
}
/**
+ * Set readable channel block size
+ *
* @param blockSize
* force a different block size for testing.
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java
index cd4f168d86..89a3afbd9b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/LargePackedWholeObject.java
@@ -44,31 +44,26 @@ final class LargePackedWholeObject extends ObjectLoader {
this.db = db;
}
- /** {@inheritDoc} */
@Override
public int getType() {
return type;
}
- /** {@inheritDoc} */
@Override
public long getSize() {
return size;
}
- /** {@inheritDoc} */
@Override
public boolean isLarge() {
return true;
}
- /** {@inheritDoc} */
@Override
public byte[] getCachedBytes() throws LargeObjectException {
throw new LargeObjectException();
}
- /** {@inheritDoc} */
@Override
public ObjectStream openStream() throws MissingObjectException, IOException {
PackInputStream packIn;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackExtBlockCacheTable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackExtBlockCacheTable.java
new file mode 100644
index 0000000000..bb44f9397a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackExtBlockCacheTable.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2024, Google LLC and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.dfs.DfsBlockCache.ReadableChannelSupplier;
+import org.eclipse.jgit.internal.storage.dfs.DfsBlockCache.Ref;
+import org.eclipse.jgit.internal.storage.dfs.DfsBlockCache.RefLoader;
+import org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheConfig.DfsBlockCachePackExtConfig;
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+
+/**
+ * A table that holds multiple cache tables accessed by {@link PackExt} types.
+ *
+ * <p>
+ * Allows the separation of entries from different {@link PackExt} types to
+ * limit churn in cache caused by entries of differing sizes.
+ * <p>
+ * Separating these tables enables the fine-tuning of cache tables per extension
+ * type.
+ */
+class PackExtBlockCacheTable implements DfsBlockCacheTable {
+ /**
+ * Table name.
+ */
+ private final String name;
+
+ private final DfsBlockCacheTable defaultBlockCacheTable;
+
+ // Holds the unique tables backing the extBlockCacheTables values.
+ private final List<DfsBlockCacheTable> blockCacheTableList;
+
+ // Holds the mapping of PackExt to DfsBlockCacheTables.
+ // The relation between the size of extBlockCacheTables entries and
+ // blockCacheTableList entries is:
+ // blockCacheTableList.size() <= extBlockCacheTables.size()
+ private final Map<PackExt, DfsBlockCacheTable> extBlockCacheTables;
+
+ /**
+ * Builds the PackExtBlockCacheTable from a list of
+ * {@link DfsBlockCachePackExtConfig}s.
+ *
+ * @param cacheConfig
+ * {@link DfsBlockCacheConfig} containing
+ * {@link DfsBlockCachePackExtConfig}s used to configure
+ * PackExtBlockCacheTable. The {@link DfsBlockCacheConfig} holds
+ * the configuration for the default cache table.
+ * @return the cache table built from the given configs.
+ * @throws IllegalArgumentException
+ * when no {@link DfsBlockCachePackExtConfig} exists in the
+ * {@link DfsBlockCacheConfig}.
+ */
+ static PackExtBlockCacheTable fromBlockCacheConfigs(
+ DfsBlockCacheConfig cacheConfig) {
+ DfsBlockCacheTable defaultTable = new ClockBlockCacheTable(cacheConfig);
+ Map<PackExt, DfsBlockCacheTable> packExtBlockCacheTables = new HashMap<>();
+ List<DfsBlockCachePackExtConfig> packExtConfigs = cacheConfig
+ .getPackExtCacheConfigurations();
+ if (packExtConfigs == null || packExtConfigs.size() == 0) {
+ throw new IllegalArgumentException(
+ JGitText.get().noPackExtConfigurationGiven);
+ }
+ for (DfsBlockCachePackExtConfig packExtCacheConfig : packExtConfigs) {
+ DfsBlockCacheTable table = new ClockBlockCacheTable(
+ packExtCacheConfig.getPackExtCacheConfiguration());
+ for (PackExt packExt : packExtCacheConfig.getPackExts()) {
+ if (packExtBlockCacheTables.containsKey(packExt)) {
+ throw new IllegalArgumentException(MessageFormat.format(
+ JGitText.get().duplicatePackExtensionsForCacheTables,
+ packExt));
+ }
+ packExtBlockCacheTables.put(packExt, table);
+ }
+ }
+ return fromCacheTables(defaultTable, packExtBlockCacheTables);
+ }
+
+ /**
+ * Creates a new PackExtBlockCacheTable from the combination of a default
+ * {@link DfsBlockCacheTable} and a map of {@link PackExt}s to
+ * {@link DfsBlockCacheTable}s.
+ * <p>
+ * This method allows for the PackExtBlockCacheTable to handle a mapping of
+ * {@link PackExt}s to arbitrarily defined {@link DfsBlockCacheTable}
+ * implementations. This is especially useful for users wishing to implement
+ * custom cache tables.
+ * <p>
+ * This is currently made visible for testing.
+ *
+ * @param defaultBlockCacheTable
+ * the default table used when a handling a {@link PackExt} type
+ * that does not map to a {@link DfsBlockCacheTable} mapped by
+ * packExtsCacheTablePairs.
+ * @param packExtBlockCacheTables
+ * the mapping of {@link PackExt}s to
+ * {@link DfsBlockCacheTable}s. A single
+ * {@link DfsBlockCacheTable} can be defined for multiple
+ * {@link PackExt}s in a many-to-one relationship.
+ * @return the PackExtBlockCacheTable created from the
+ * defaultBlockCacheTable and packExtsCacheTablePairs mapping.
+ * @throws IllegalArgumentException
+ * when a {@link PackExt} is defined for multiple
+ * {@link DfsBlockCacheTable}s.
+ */
+ static PackExtBlockCacheTable fromCacheTables(
+ DfsBlockCacheTable defaultBlockCacheTable,
+ Map<PackExt, DfsBlockCacheTable> packExtBlockCacheTables) {
+ Set<DfsBlockCacheTable> blockCacheTables = new HashSet<>();
+ blockCacheTables.add(defaultBlockCacheTable);
+ blockCacheTables.addAll(packExtBlockCacheTables.values());
+ String name = defaultBlockCacheTable.getName() + "," //$NON-NLS-1$
+ + packExtBlockCacheTables.values().stream()
+ .map(DfsBlockCacheTable::getName).sorted()
+ .collect(Collectors.joining(",")); //$NON-NLS-1$
+ return new PackExtBlockCacheTable(name, defaultBlockCacheTable,
+ List.copyOf(blockCacheTables), packExtBlockCacheTables);
+ }
+
+ private PackExtBlockCacheTable(String name,
+ DfsBlockCacheTable defaultBlockCacheTable,
+ List<DfsBlockCacheTable> blockCacheTableList,
+ Map<PackExt, DfsBlockCacheTable> extBlockCacheTables) {
+ this.name = name;
+ this.defaultBlockCacheTable = defaultBlockCacheTable;
+ this.blockCacheTableList = blockCacheTableList;
+ this.extBlockCacheTables = extBlockCacheTables;
+ }
+
+ @Override
+ public boolean hasBlock0(DfsStreamKey key) {
+ return getTable(key).hasBlock0(key);
+ }
+
+ @Override
+ public DfsBlock getOrLoad(BlockBasedFile file, long position,
+ DfsReader dfsReader, ReadableChannelSupplier fileChannel)
+ throws IOException {
+ return getTable(file.ext).getOrLoad(file, position, dfsReader,
+ fileChannel);
+ }
+
+ @Override
+ public <T> Ref<T> getOrLoadRef(DfsStreamKey key, long position,
+ RefLoader<T> loader) throws IOException {
+ return getTable(key).getOrLoadRef(key, position, loader);
+ }
+
+ @Override
+ public void put(DfsBlock v) {
+ getTable(v.stream).put(v);
+ }
+
+ @Override
+ public <T> Ref<T> put(DfsStreamKey key, long pos, long size, T v) {
+ return getTable(key).put(key, pos, size, v);
+ }
+
+ @Override
+ public <T> Ref<T> putRef(DfsStreamKey key, long size, T v) {
+ return getTable(key).putRef(key, size, v);
+ }
+
+ @Override
+ public boolean contains(DfsStreamKey key, long position) {
+ return getTable(key).contains(key, position);
+ }
+
+ @Override
+ public <T> T get(DfsStreamKey key, long position) {
+ return getTable(key).get(key, position);
+ }
+
+ @Override
+ public List<BlockCacheStats> getBlockCacheStats() {
+ return blockCacheTableList.stream()
+ .flatMap(cacheTable -> cacheTable.getBlockCacheStats().stream())
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ private DfsBlockCacheTable getTable(PackExt packExt) {
+ return extBlockCacheTables.getOrDefault(packExt,
+ defaultBlockCacheTable);
+ }
+
+ private DfsBlockCacheTable getTable(DfsStreamKey key) {
+ return extBlockCacheTables.getOrDefault(getPackExt(key),
+ defaultBlockCacheTable);
+ }
+
+ private static PackExt getPackExt(DfsStreamKey key) {
+ return PackExt.values()[key.packExtPos];
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackInputStream.java
index 6ff81646cf..3d07660a60 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/PackInputStream.java
@@ -31,7 +31,6 @@ final class PackInputStream extends InputStream {
ctx.pin(pack, pos);
}
- /** {@inheritDoc} */
@Override
public int read(byte[] b, int off, int len) throws IOException {
int n = ctx.copy(pack, pos, b, off, len);
@@ -39,7 +38,6 @@ final class PackInputStream extends InputStream {
return n;
}
- /** {@inheritDoc} */
@Override
public int read() throws IOException {
byte[] buf = new byte[1];
@@ -47,7 +45,6 @@ final class PackInputStream extends InputStream {
return n == 1 ? buf[0] & 0xff : -1;
}
- /** {@inheritDoc} */
@Override
public void close() {
ctx.close();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java
index ec53818b4e..5f979b0daa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackBitmapIndex.java
@@ -18,14 +18,13 @@ import com.googlecode.javaewah.EWAHCompressedBitmap;
/**
* Base implementation of the PackBitmapIndex.
*/
-abstract class BasePackBitmapIndex extends PackBitmapIndex {
+abstract class BasePackBitmapIndex implements PackBitmapIndex {
private final ObjectIdOwnerMap<StoredBitmap> bitmaps;
BasePackBitmapIndex(ObjectIdOwnerMap<StoredBitmap> bitmaps) {
this.bitmaps = bitmaps;
}
- /** {@inheritDoc} */
@Override
public EWAHCompressedBitmap getBitmap(AnyObjectId objectId) {
StoredBitmap sb = bitmaps.get(objectId);
@@ -36,6 +35,50 @@ abstract class BasePackBitmapIndex extends PackBitmapIndex {
return bitmaps;
}
+ @Override
+ public int getBaseBitmapCount() {
+ int bases = 0;
+ for (StoredBitmap sb : getBitmaps()) {
+ if (sb.isBase()) {
+ bases += 1;
+ }
+ }
+ return bases;
+ }
+
+ @Override
+ public long getBaseBitmapSizeInBytes() {
+ long baseSize = 0;
+ for (StoredBitmap sb : getBitmaps()) {
+ if (sb.isBase()) {
+ baseSize += sb.getCurrentSizeInBytes();
+ }
+ }
+ return baseSize;
+ }
+
+ @Override
+ public int getXorBitmapCount() {
+ int xored = 0;
+ for (StoredBitmap sb : getBitmaps()) {
+ if (!sb.isBase()) {
+ xored += 1;
+ }
+ }
+ return xored;
+ }
+
+ @Override
+ public long getXorBitmapSizeInBytes() {
+ long xorSize = 0;
+ for (StoredBitmap sb : getBitmaps()) {
+ if (!sb.isBase()) {
+ xorSize += sb.getCurrentSizeInBytes();
+ }
+ }
+ return xorSize;
+ }
+
/**
* Data representation of the bitmap entry restored from a pack index. The
* commit of the bitmap is the map key.
@@ -75,8 +118,9 @@ abstract class BasePackBitmapIndex extends PackBitmapIndex {
EWAHCompressedBitmap getBitmapWithoutCaching() {
// Fast path to immediately return the expanded result.
Object r = bitmapContainer;
- if (r instanceof EWAHCompressedBitmap)
+ if (r instanceof EWAHCompressedBitmap) {
return (EWAHCompressedBitmap) r;
+ }
// Expand the bitmap but not cache the result.
XorCompressedBitmap xb = (XorCompressedBitmap) r;
@@ -93,14 +137,46 @@ abstract class BasePackBitmapIndex extends PackBitmapIndex {
}
}
- /** @return the flags associated with the bitmap */
+ /**
+ * Get flags
+ *
+ * @return the flags associated with the bitmap
+ */
int getFlags() {
return flags;
}
+
+ /**
+ * This bitmap is (currently) a base or a XOR mask
+ *
+ * @return true if this bitmap is a base (a ready map).
+ */
+ boolean isBase() {
+ return bitmapContainer instanceof EWAHCompressedBitmap;
+ }
+
+ /**
+ * Size in bytes of this bitmap in its current representation
+ *
+ * If this is a XOR'ed bitmap, size is different before/after
+ * {@link #getBitmap()}. Before is the byte size of the xor mask,
+ * afterwards is the size of the "ready" bitmap
+ *
+ * @return size in bytes of the bitmap in its current representation
+ */
+ long getCurrentSizeInBytes() {
+ Object r = bitmapContainer;
+ if (r instanceof EWAHCompressedBitmap) {
+ return ((EWAHCompressedBitmap) r).sizeInBytes();
+ }
+ XorCompressedBitmap xor = ((XorCompressedBitmap) r);
+ return xor.bitmap.sizeInBytes();
+ }
}
private static final class XorCompressedBitmap {
final EWAHCompressedBitmap bitmap;
+
final StoredBitmap xorBitmap;
XorCompressedBitmap(EWAHCompressedBitmap b, StoredBitmap xb) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackIndexWriter.java
index 87e0b44d46..b89cc1ebf4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackIndexWriter.java
@@ -19,6 +19,7 @@ import java.text.MessageFormat;
import java.util.List;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.pack.PackIndexWriter;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.transport.PackedObjectInfo;
import org.eclipse.jgit.util.NB;
@@ -31,7 +32,7 @@ import org.eclipse.jgit.util.NB;
* 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 {
+public abstract class BasePackIndexWriter implements PackIndexWriter {
/** Magic constant indicating post-version 1 format. */
protected static final byte[] TOC = { -1, 't', 'O', 'c' };
@@ -147,7 +148,7 @@ public abstract class PackIndexWriter {
* the stream this instance outputs to. If not already buffered
* it will be automatically wrapped in a buffered stream.
*/
- protected PackIndexWriter(OutputStream dst) {
+ protected BasePackIndexWriter(OutputStream dst) {
out = new DigestOutputStream(dst instanceof BufferedOutputStream ? dst
: new BufferedOutputStream(dst),
Constants.newMessageDigest());
@@ -172,6 +173,7 @@ public abstract class PackIndexWriter {
* an error occurred while writing to the output stream, or this
* index format cannot store the object data supplied.
*/
+ @Override
public void write(final List<? extends PackedObjectInfo> toStore,
final byte[] packDataChecksum) throws IOException {
entries = toStore;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java
index 9aa14171c7..ba6929308c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BitmapIndexImpl.java
@@ -38,6 +38,8 @@ public class BitmapIndexImpl implements BitmapIndex {
final int indexObjectCount;
+ private BitmapLookupListener listener = BitmapLookupListener.NOOP;
+
/**
* Creates a BitmapIndex that is back by Compressed bitmaps.
*
@@ -54,21 +56,31 @@ public class BitmapIndexImpl implements BitmapIndex {
return packIndex;
}
- /** {@inheritDoc} */
@Override
public CompressedBitmap getBitmap(AnyObjectId objectId) {
EWAHCompressedBitmap compressed = packIndex.getBitmap(objectId);
- if (compressed == null)
+ if (compressed == null) {
+ listener.onBitmapNotFound(objectId);
return null;
+ }
+ listener.onBitmapFound(objectId);
return new CompressedBitmap(compressed, this);
}
- /** {@inheritDoc} */
@Override
public CompressedBitmapBuilder newBitmapBuilder() {
return new CompressedBitmapBuilder(this);
}
+ @Override
+ public void addBitmapLookupListener(BitmapLookupListener l) {
+ if (l == null) {
+ throw new IllegalArgumentException(
+ JGitText.get().bitmapUseNoopNoListener);
+ }
+ this.listener = l;
+ }
+
int findPosition(AnyObjectId objectId) {
int position = packIndex.findPosition(objectId);
if (position < 0) {
@@ -88,6 +100,11 @@ public class BitmapIndexImpl implements BitmapIndex {
return position;
}
+ /**
+ * A bitset for representing small changes (set/remove individual bits)
+ * relative to an existing EWAH bitmap. Executing bit-vector operations will
+ * materialize the changes into a fresh EWAH bitmap
+ */
private static final class ComboBitset {
private InflatingBitSet inflatingBitmap;
@@ -123,18 +140,21 @@ public class BitmapIndexImpl implements BitmapIndex {
return inflatingBitmap.getBitmap();
}
+ /* In-place or operation */
void or(EWAHCompressedBitmap inbits) {
if (toRemove != null)
combine();
inflatingBitmap = inflatingBitmap.or(inbits);
}
+ /* In-place andNot operation */
void andNot(EWAHCompressedBitmap inbits) {
if (toAdd != null || toRemove != null)
combine();
inflatingBitmap = inflatingBitmap.andNot(inbits);
}
+ /* In-place xor operation. */
void xor(EWAHCompressedBitmap inbits) {
if (toAdd != null || toRemove != null)
combine();
@@ -291,7 +311,9 @@ public class BitmapIndexImpl implements BitmapIndex {
* Construct compressed bitmap for given bitmap and bitmap index
*
* @param bitmap
+ * the bitmap
* @param bitmapIndex
+ * the bitmap index
*/
public CompressedBitmap(EWAHCompressedBitmap bitmap, BitmapIndexImpl bitmapIndex) {
this.bitmap = bitmap;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java
index 1036535423..ef1392e6e0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java
@@ -30,7 +30,6 @@ final class ByteArrayWindow extends ByteWindow {
array = b;
}
- /** {@inheritDoc} */
@Override
protected int copy(int p, byte[] b, int o, int n) {
n = Math.min(array.length - p, n);
@@ -38,7 +37,6 @@ final class ByteArrayWindow extends ByteWindow {
return n;
}
- /** {@inheritDoc} */
@Override
protected int setInput(int pos, Inflater inf)
throws DataFormatException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java
index b6877578c9..4094f130f6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java
@@ -32,7 +32,6 @@ final class ByteBufferWindow extends ByteWindow {
buffer = b;
}
- /** {@inheritDoc} */
@Override
protected int copy(int p, byte[] b, int o, int n) {
final ByteBuffer s = buffer.slice();
@@ -57,7 +56,6 @@ final class ByteBufferWindow extends ByteWindow {
}
}
- /** {@inheritDoc} */
@Override
protected int setInput(int pos, Inflater inf)
throws DataFormatException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java
index 2e19580f5f..129d2e0894 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java
@@ -86,13 +86,11 @@ class CachedObjectDirectory extends FileObjectDatabase {
return m;
}
- /** {@inheritDoc} */
@Override
public void close() {
// Don't close anything.
}
- /** {@inheritDoc} */
@Override
public ObjectDatabase newCachedDatabase() {
return this;
@@ -153,7 +151,6 @@ class CachedObjectDirectory extends FileObjectDatabase {
wrapped.resolve(matches, id);
}
- /** {@inheritDoc} */
@Override
public boolean has(AnyObjectId objectId) throws IOException {
return has(objectId, null);
@@ -261,7 +258,6 @@ class CachedObjectDirectory extends FileObjectDatabase {
return wrapped.getPacks();
}
- /** {@inheritDoc} */
@Override
public Optional<CommitGraph> getCommitGraph() {
return wrapped.getCommitGraph();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java
index 2b8779f6df..d641da59e1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CheckoutEntryImpl.java
@@ -32,13 +32,11 @@ public class CheckoutEntryImpl implements CheckoutEntry {
to = comment.substring(p2 + " to ".length(), p3); //$NON-NLS-1$
}
- /** {@inheritDoc} */
@Override
public String getFromBranch() {
return from;
}
- /** {@inheritDoc} */
@Override
public String getToBranch() {
return to;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileCommitGraph.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileCommitGraph.java
index 44429a7786..5172963e2e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileCommitGraph.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileCommitGraph.java
@@ -108,7 +108,8 @@ public class FileCommitGraph {
// commit-graph file was not modified
return this;
}
- return new GraphSnapshot(file, FileSnapshot.save(file), open(file));
+ return new GraphSnapshot(file, FileSnapshot.save(file),
+ open(file));
}
private static CommitGraph open(File file) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java
index aa578d31ba..e8f532fe24 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java
@@ -33,13 +33,11 @@ abstract class FileObjectDatabase extends ObjectDatabase {
INSERTED, EXISTS_PACKED, EXISTS_LOOSE, FAILURE;
}
- /** {@inheritDoc} */
@Override
public ObjectReader newReader() {
return new WindowCursor(this);
}
- /** {@inheritDoc} */
@Override
public ObjectDirectoryInserter newInserter() {
return new ObjectDirectoryInserter(this, getConfig());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java
index e9e17c0ec5..64f8c9b0e3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java
@@ -16,6 +16,7 @@ import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
import java.io.File;
import java.io.IOException;
+import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -28,17 +29,21 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.api.PackRefsCommand;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.events.RefsChangedEvent;
+import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.reftable.MergedReftable;
import org.eclipse.jgit.internal.storage.reftable.ReftableBatchRefUpdate;
import org.eclipse.jgit.internal.storage.reftable.ReftableDatabase;
import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.RefRename;
@@ -67,15 +72,15 @@ public class FileReftableDatabase extends RefDatabase {
private final FileReftableStack reftableStack;
- FileReftableDatabase(FileRepository repo) throws IOException {
- this(repo, new File(new File(repo.getDirectory(), Constants.REFTABLE),
- Constants.TABLES_LIST));
- }
+ private volatile boolean autoRefresh;
- FileReftableDatabase(FileRepository repo, File refstackName) throws IOException {
+ FileReftableDatabase(FileRepository repo) throws IOException {
this.fileRepository = repo;
- this.reftableStack = new FileReftableStack(refstackName,
- new File(fileRepository.getDirectory(), Constants.REFTABLE),
+ this.autoRefresh = repo.getConfig().getBoolean(
+ ConfigConstants.CONFIG_REFTABLE_SECTION,
+ ConfigConstants.CONFIG_KEY_AUTOREFRESH, false);
+ this.reftableStack = new FileReftableStack(
+ new File(fileRepository.getCommonDirectory(), Constants.REFTABLE),
() -> fileRepository.fireEvent(new RefsChangedEvent()),
() -> fileRepository.getConfig());
this.reftableDatabase = new ReftableDatabase() {
@@ -87,25 +92,49 @@ public class FileReftableDatabase extends RefDatabase {
};
}
- ReflogReader getReflogReader(String refname) throws IOException {
+ @Override
+ public ReflogReader getReflogReader(Ref ref) throws IOException {
+ return reftableDatabase.getReflogReader(ref.getName());
+ }
+
+ @Override
+ public ReflogReader getReflogReader(String refname) throws IOException {
return reftableDatabase.getReflogReader(refname);
}
/**
+ * Whether the given repo uses reftable for refdb storage
+ *
* @param repoDir
+ * the repository's metadata directory
* @return whether the given repo uses reftable for refdb storage.
*/
public static boolean isReftable(File repoDir) {
return new File(repoDir, Constants.REFTABLE).isDirectory();
}
- /** {@inheritDoc} */
@Override
public boolean hasFastTipsWithSha1() throws IOException {
return reftableDatabase.hasFastTipsWithSha1();
}
/**
+ * {@inheritDoc}
+ *
+ * For Reftable, all the data is compacted into a single table.
+ */
+ @Override
+ public void packRefs(ProgressMonitor pm, PackRefsCommand packRefs)
+ throws IOException {
+ pm.beginTask(JGitText.get().packRefs, 1);
+ try {
+ compactFully();
+ } finally {
+ pm.endTask();
+ }
+ }
+
+ /**
* Runs a full compaction for GC purposes.
* @throws IOException on I/O errors
*/
@@ -124,20 +153,17 @@ public class FileReftableDatabase extends RefDatabase {
return reftableDatabase.getLock();
}
- /** {@inheritDoc} */
@Override
public boolean performsAtomicTransactions() {
return true;
}
- /** {@inheritDoc} */
@NonNull
@Override
public BatchRefUpdate newBatchUpdate() {
return new FileReftableBatchRefUpdate(this, fileRepository);
}
- /** {@inheritDoc} */
@Override
public RefUpdate newUpdate(String refName, boolean detach)
throws IOException {
@@ -157,21 +183,20 @@ public class FileReftableDatabase extends RefDatabase {
return update;
}
- /** {@inheritDoc} */
@Override
public Ref exactRef(String name) throws IOException {
+ autoRefresh();
return reftableDatabase.exactRef(name);
}
- /** {@inheritDoc} */
@Override
public List<Ref> getRefs() throws IOException {
return super.getRefs();
}
- /** {@inheritDoc} */
@Override
public Map<String, Ref> getRefs(String prefix) throws IOException {
+ autoRefresh();
List<Ref> refs = reftableDatabase.getRefsByPrefix(prefix);
RefList.Builder<Ref> builder = new RefList.Builder<>(refs.size());
for (Ref r : refs) {
@@ -181,20 +206,18 @@ public class FileReftableDatabase extends RefDatabase {
RefList.emptyList());
}
- /** {@inheritDoc} */
@Override
public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> excludes)
throws IOException {
+ autoRefresh();
return reftableDatabase.getRefsByPrefixWithExclusions(include, excludes);
}
- /** {@inheritDoc} */
@Override
public List<Ref> getAdditionalRefs() throws IOException {
return Collections.emptyList();
}
- /** {@inheritDoc} */
@Override
public Ref peel(Ref ref) throws IOException {
Ref oldLeaf = ref.getLeaf();
@@ -205,6 +228,56 @@ public class FileReftableDatabase extends RefDatabase {
}
+ /**
+ * Whether to auto-refresh the reftable stack if it is out of date.
+ *
+ * @param autoRefresh
+ * whether to auto-refresh the reftable stack if it is out of
+ * date.
+ */
+ public void setAutoRefresh(boolean autoRefresh) {
+ this.autoRefresh = autoRefresh;
+ }
+
+ /**
+ * Whether the reftable stack is auto-refreshed if it is out of date.
+ *
+ * @return whether the reftable stack is auto-refreshed if it is out of
+ * date.
+ */
+ public boolean isAutoRefresh() {
+ return autoRefresh;
+ }
+
+ private void autoRefresh() {
+ if (autoRefresh) {
+ refresh();
+ }
+ }
+
+ /**
+ * Check if the reftable stack is up to date, and if not, reload it.
+ * <p>
+ * {@inheritDoc}
+ */
+ @Override
+ public void refresh() {
+ try {
+ if (!reftableStack.isUpToDate()) {
+ ReentrantLock lock = getLock();
+ lock.lock();
+ try {
+ reftableDatabase.clearCache();
+ reftableStack.reload();
+ } finally {
+ lock.unlock();
+ }
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
private Ref doPeel(Ref leaf) throws IOException {
try (RevWalk rw = new RevWalk(fileRepository)) {
RevObject obj = rw.parseAny(leaf.getObjectId());
@@ -303,7 +376,6 @@ public class FileReftableDatabase extends RefDatabase {
}
}
- /** {@inheritDoc} */
@Override
public RefRename newRename(String fromName, String toName)
throws IOException {
@@ -312,24 +384,21 @@ public class FileReftableDatabase extends RefDatabase {
return new FileRefRename(src, dst);
}
- /** {@inheritDoc} */
@Override
public boolean isNameConflicting(String name) throws IOException {
return reftableDatabase.isNameConflicting(name, new TreeSet<>(),
new HashSet<>());
}
- /** {@inheritDoc} */
@Override
public void close() {
reftableStack.close();
}
- /** {@inheritDoc} */
@Override
public void create() throws IOException {
FileUtils.mkdir(
- new File(fileRepository.getDirectory(), Constants.REFTABLE),
+ new File(fileRepository.getCommonDirectory(), Constants.REFTABLE),
true);
}
@@ -549,9 +618,10 @@ public class FileReftableDatabase extends RefDatabase {
boolean writeLogs) throws IOException {
int size = 0;
List<Ref> refs = repo.getRefDatabase().getRefs();
+ RefDatabase refDb = repo.getRefDatabase();
if (writeLogs) {
for (Ref r : refs) {
- ReflogReader rlr = repo.getReflogReader(r.getName());
+ ReflogReader rlr = refDb.getReflogReader(r);
if (rlr != null) {
size = Math.max(rlr.getReverseEntries().size(), size);
}
@@ -574,10 +644,7 @@ public class FileReftableDatabase extends RefDatabase {
if (writeLogs) {
for (Ref r : refs) {
long idx = size;
- ReflogReader reader = repo.getReflogReader(r.getName());
- if (reader == null) {
- continue;
- }
+ ReflogReader reader = refDb.getReflogReader(r);
for (ReflogEntry e : reader.getReverseEntries()) {
w.writeLog(r.getName(), idx, e.getWho(), e.getOldId(),
e.getNewId(), e.getComment());
@@ -611,36 +678,26 @@ public class FileReftableDatabase extends RefDatabase {
}
/**
+ * Convert FileRepository to a FileReftableDatabase
+ *
* @param repo
* the repository
* @param writeLogs
* whether to write reflogs
- * @return a reftable based RefDB from an existing repository.
* @throws IOException
* on IO error
*/
- public static FileReftableDatabase convertFrom(FileRepository repo,
- boolean writeLogs) throws IOException {
- FileReftableDatabase newDb = null;
- File reftableList = null;
- try {
- File reftableDir = new File(repo.getDirectory(),
- Constants.REFTABLE);
- reftableList = new File(reftableDir, Constants.TABLES_LIST);
- if (!reftableDir.isDirectory()) {
- reftableDir.mkdir();
- }
+ public static void convertFrom(FileRepository repo, boolean writeLogs)
+ throws IOException {
+ File reftableDir = new File(repo.getCommonDirectory(),
+ Constants.REFTABLE);
+ if (!reftableDir.isDirectory()) {
+ reftableDir.mkdir();
+ }
- try (FileReftableStack stack = new FileReftableStack(reftableList,
- reftableDir, null, () -> repo.getConfig())) {
- stack.addReftable(rw -> writeConvertTable(repo, rw, writeLogs));
- }
- reftableList = null;
- } finally {
- if (reftableList != null) {
- reftableList.delete();
- }
+ try (FileReftableStack stack = new FileReftableStack(reftableDir, null,
+ () -> repo.getConfig())) {
+ stack.addReftable(rw -> writeConvertTable(repo, rw, writeLogs));
}
- return newDb;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java
index 5152367d23..6658575fc5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java
@@ -18,8 +18,10 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
import java.nio.file.StandardCopyOption;
import java.security.SecureRandom;
import java.util.ArrayList;
@@ -27,6 +29,7 @@ import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@@ -39,6 +42,9 @@ import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
import org.eclipse.jgit.internal.storage.reftable.ReftableReader;
import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.CoreConfig.TrustStat;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.SystemReader;
@@ -59,9 +65,12 @@ public class FileReftableStack implements AutoCloseable {
private List<StackEntry> stack;
+ private AtomicReference<FileSnapshot> snapshot = new AtomicReference<>(
+ FileSnapshot.DIRTY);
+
private long lastNextUpdateIndex;
- private final File stackPath;
+ private final File tablesListFile;
private final File reftableDir;
@@ -98,11 +107,11 @@ public class FileReftableStack implements AutoCloseable {
private final CompactionStats stats;
+ private final TrustStat trustTablesListStat;
+
/**
* Creates a stack corresponding to the list of reftables in the argument
*
- * @param stackPath
- * the filename for the stack.
* @param reftableDir
* the dir holding the tables.
* @param onChange
@@ -112,10 +121,10 @@ public class FileReftableStack implements AutoCloseable {
* @throws IOException
* on I/O problems
*/
- public FileReftableStack(File stackPath, File reftableDir,
+ public FileReftableStack(File reftableDir,
@Nullable Runnable onChange, Supplier<Config> configSupplier)
throws IOException {
- this.stackPath = stackPath;
+ this.tablesListFile = new File(reftableDir, Constants.TABLES_LIST);
this.reftableDir = reftableDir;
this.stack = new ArrayList<>();
this.configSupplier = configSupplier;
@@ -126,6 +135,8 @@ public class FileReftableStack implements AutoCloseable {
reload();
stats = new CompactionStats();
+ trustTablesListStat = configSupplier.get().get(CoreConfig.KEY)
+ .getTrustTablesListStat();
}
CompactionStats getStats() {
@@ -232,7 +243,7 @@ public class FileReftableStack implements AutoCloseable {
}
if (!success) {
- throw new LockFailedException(stackPath);
+ throw new LockFailedException(tablesListFile);
}
mergedReftable = new MergedReftable(stack.stream()
@@ -246,6 +257,8 @@ public class FileReftableStack implements AutoCloseable {
}
/**
+ * Get merged reftable
+ *
* @return the merged reftable
*/
public MergedReftable getMergedReftable() {
@@ -264,23 +277,27 @@ public class FileReftableStack implements AutoCloseable {
* @param w
* writer to use
* @throws IOException
+ * if an IO error occurred
*/
void call(ReftableWriter w) throws IOException;
}
private List<String> readTableNames() throws IOException {
+ FileSnapshot old;
List<String> names = new ArrayList<>(stack.size() + 1);
-
+ old = snapshot.get();
try (BufferedReader br = new BufferedReader(
- new InputStreamReader(new FileInputStream(stackPath), UTF_8))) {
+ new InputStreamReader(new FileInputStream(tablesListFile), UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
if (!line.isEmpty()) {
names.add(line);
}
}
+ snapshot.compareAndSet(old, FileSnapshot.save(tablesListFile));
} catch (FileNotFoundException e) {
// file isn't there: empty repository.
+ snapshot.compareAndSet(old, FileSnapshot.MISSING_FILE);
}
return names;
}
@@ -291,9 +308,29 @@ public class FileReftableStack implements AutoCloseable {
* on IO problem
*/
boolean isUpToDate() throws IOException {
- // We could use FileSnapshot to avoid reading the file, but the file is
- // small so it's probably a minor optimization.
try {
+ switch (trustTablesListStat) {
+ case NEVER:
+ break;
+ case AFTER_OPEN:
+ try (InputStream stream = Files
+ .newInputStream(reftableDir.toPath())) {
+ // open the refs/reftable/ directory to refresh attributes
+ // of reftable files and the tables.list file listing their
+ // names (on some NFS clients)
+ } catch (FileNotFoundException | NoSuchFileException e) {
+ // ignore
+ }
+ //$FALL-THROUGH$
+ case ALWAYS:
+ if (!snapshot.get().isModified(tablesListFile)) {
+ return true;
+ }
+ break;
+ case INHERIT:
+ // only used in CoreConfig internally
+ throw new IllegalStateException();
+ }
List<String> names = readTableNames();
if (names.size() != stack.size()) {
return false;
@@ -350,7 +387,7 @@ public class FileReftableStack implements AutoCloseable {
*/
@SuppressWarnings("nls")
public boolean addReftable(Writer w) throws IOException {
- LockFile lock = new LockFile(stackPath);
+ LockFile lock = new LockFile(tablesListFile);
try {
if (!lock.lockForAppend()) {
return false;
@@ -361,8 +398,7 @@ public class FileReftableStack implements AutoCloseable {
String fn = filename(nextUpdateIndex(), nextUpdateIndex());
- File tmpTable = File.createTempFile(fn + "_", ".ref",
- stackPath.getParentFile());
+ File tmpTable = File.createTempFile(fn + "_", ".ref", reftableDir);
ReftableWriter.Stats s;
try (FileOutputStream fos = new FileOutputStream(tmpTable)) {
@@ -416,7 +452,7 @@ public class FileReftableStack implements AutoCloseable {
String fn = filename(first, last);
File tmpTable = File.createTempFile(fn + "_", ".ref", //$NON-NLS-1$//$NON-NLS-2$
- stackPath.getParentFile());
+ reftableDir);
try (FileOutputStream fos = new FileOutputStream(tmpTable)) {
ReftableCompactor c = new ReftableCompactor(fos)
.setConfig(reftableConfig())
@@ -460,7 +496,7 @@ public class FileReftableStack implements AutoCloseable {
if (first >= last) {
return true;
}
- LockFile lock = new LockFile(stackPath);
+ LockFile lock = new LockFile(tablesListFile);
File tmpTable = null;
List<LockFile> subtableLocks = new ArrayList<>();
@@ -489,7 +525,7 @@ public class FileReftableStack implements AutoCloseable {
tmpTable = compactLocked(first, last);
- lock = new LockFile(stackPath);
+ lock = new LockFile(tablesListFile);
if (!lock.lock()) {
return false;
}
@@ -559,6 +595,7 @@ public class FileReftableStack implements AutoCloseable {
* Calculate an approximate log2.
*
* @param sz
+ * the number to compute an approximate log2 for
* @return log2
*/
static int log(long sz) {
@@ -615,6 +652,9 @@ public class FileReftableStack implements AutoCloseable {
if (other == null) {
return false;
}
+ if (!(other instanceof Segment)) {
+ return false;
+ }
Segment o = (Segment) other;
return o.bytes == bytes && o.log == log && o.start == start
&& o.end == end;
@@ -688,6 +728,7 @@ public class FileReftableStack implements AutoCloseable {
* shape.
*
* @throws IOException
+ * if an IO error occurred
*/
private void autoCompact() throws IOException {
Optional<Segment> cand = autoCompactCandidate(tableSizes());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
index 3e92cddacd..bcf9f1efdf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
@@ -2,7 +2,7 @@
* Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
* Copyright (C) 2008-2010, Google Inc.
* Copyright (C) 2006-2010, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2006-2024, Shawn O. Pearce <spearce@spearce.org> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -31,8 +31,8 @@ import java.util.Locale;
import java.util.Objects;
import java.util.Set;
-import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.attributes.AttributesNode;
import org.eclipse.jgit.attributes.AttributesNodeProvider;
@@ -60,7 +60,6 @@ import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
-import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.IO;
@@ -165,7 +164,7 @@ public class FileRepository extends Repository {
throw new IOException(e.getMessage(), e);
}
repoConfig = new FileBasedConfig(userConfig, getFS().resolve(
- getDirectory(), Constants.CONFIG),
+ getCommonDirectory(), Constants.CONFIG),
getFS());
loadRepoConfig();
@@ -193,7 +192,7 @@ public class FileRepository extends Repository {
options.getObjectDirectory(), //
options.getAlternateObjectDirectories(), //
getFS(), //
- new File(getDirectory(), Constants.SHALLOW));
+ new File(getCommonDirectory(), Constants.SHALLOW));
if (objectDatabase.exists()) {
if (repositoryFormatVersion > 1)
@@ -215,6 +214,16 @@ public class FileRepository extends Repository {
}
}
+ private String getRelativeDir(File base, File other) {
+ File relPath;
+ try {
+ relPath = base.toPath().relativize(other.toPath()).toFile();
+ } catch (IllegalArgumentException e) {
+ relPath = other;
+ }
+ return FileUtils.pathToString(relPath);
+ }
+
/**
* {@inheritDoc}
* <p>
@@ -223,6 +232,22 @@ public class FileRepository extends Repository {
*/
@Override
public void create(boolean bare) throws IOException {
+ create(bare, false);
+ }
+
+ /**
+ * Create a new Git repository initializing the necessary files and
+ * directories.
+ *
+ * @param bare
+ * if true, a bare repository (a repository without a working
+ * directory) is created.
+ * @param relativePaths
+ * if true, relative paths are used for GIT_DIR and GIT_WORK_TREE
+ * @throws IOException
+ * in case of IO problem
+ */
+ public void create(boolean bare, boolean relativePaths) throws IOException {
final FileBasedConfig cfg = getConfig();
if (cfg.getFile().exists()) {
throw new IllegalStateException(MessageFormat.format(
@@ -293,15 +318,25 @@ public class FileRepository extends Repository {
if (!bare) {
File workTree = getWorkTree();
if (!getDirectory().getParentFile().equals(workTree)) {
+ String workTreePath;
+ String gitDirPath;
+ if (relativePaths) {
+ File canonGitDir = getDirectory().getCanonicalFile();
+ File canonWorkTree = getWorkTree().getCanonicalFile();
+ workTreePath = getRelativeDir(canonGitDir, canonWorkTree);
+ gitDirPath = getRelativeDir(canonWorkTree, canonGitDir);
+ } else {
+ workTreePath = getWorkTree().getAbsolutePath();
+ gitDirPath = getDirectory().getAbsolutePath();
+ }
cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
- ConfigConstants.CONFIG_KEY_WORKTREE, getWorkTree()
- .getAbsolutePath());
+ ConfigConstants.CONFIG_KEY_WORKTREE, workTreePath);
LockFile dotGitLockFile = new LockFile(new File(workTree,
Constants.DOT_GIT));
try {
if (dotGitLockFile.lock()) {
dotGitLockFile.write(Constants.encode(Constants.GITDIR
- + getDirectory().getAbsolutePath()));
+ + gitDirPath));
dotGitLockFile.commit();
}
} finally {
@@ -321,19 +356,16 @@ public class FileRepository extends Repository {
return objectDatabase.getDirectory();
}
- /** {@inheritDoc} */
@Override
public ObjectDirectory getObjectDatabase() {
return objectDatabase;
}
- /** {@inheritDoc} */
@Override
public RefDatabase getRefDatabase() {
return refs;
}
- /** {@inheritDoc} */
@Override
public String getIdentifier() {
File directory = getDirectory();
@@ -343,7 +375,6 @@ public class FileRepository extends Repository {
throw new IllegalStateException();
}
- /** {@inheritDoc} */
@Override
public FileBasedConfig getConfig() {
try {
@@ -357,7 +388,6 @@ public class FileRepository extends Repository {
return repoConfig;
}
- /** {@inheritDoc} */
@Override
@Nullable
public String getGitwebDescription() throws IOException {
@@ -376,7 +406,6 @@ public class FileRepository extends Repository {
return d;
}
- /** {@inheritDoc} */
@Override
public void setGitwebDescription(@Nullable String description)
throws IOException {
@@ -423,6 +452,7 @@ public class FileRepository extends Repository {
* client trying to push changes avoid pushing more than it needs to.
*
* @throws IOException
+ * if an IO error occurred
*/
@Override
public Set<ObjectId> getAdditionalHaves() throws IOException {
@@ -478,7 +508,6 @@ public class FileRepository extends Repository {
objectDatabase.openPack(pack);
}
- /** {@inheritDoc} */
@Override
public void scanForRepoChanges() throws IOException {
getRefDatabase().getRefs(); // This will look for changes to refs
@@ -504,7 +533,6 @@ public class FileRepository extends Repository {
notifyIndexChanged(false);
}
- /** {@inheritDoc} */
@Override
public void notifyIndexChanged(boolean internal) {
synchronized (snapshotLock) {
@@ -513,31 +541,6 @@ public class FileRepository extends Repository {
fireEvent(new IndexChangedEvent(internal));
}
- /** {@inheritDoc} */
- @Override
- public ReflogReader getReflogReader(String refName) throws IOException {
- if (refs instanceof FileReftableDatabase) {
- // Cannot use findRef: reftable stores log data for deleted or renamed
- // branches.
- return ((FileReftableDatabase)refs).getReflogReader(refName);
- }
-
- // TODO: use exactRef here, which offers more predictable and therefore preferable
- // behavior.
- Ref ref = findRef(refName);
- if (ref == null) {
- return null;
- }
- return new ReflogReaderImpl(this, ref.getName());
- }
-
- @Override
- public @NonNull ReflogReader getReflogReader(@NonNull Ref ref)
- throws IOException {
- return new ReflogReaderImpl(this, ref.getName());
- }
-
- /** {@inheritDoc} */
@Override
public AttributesNodeProvider createAttributesNodeProvider() {
return new AttributesNodeProviderImpl(this);
@@ -600,18 +603,16 @@ public class FileRepository extends Repository {
ConfigConstants.CONFIG_KEY_AUTODETACH, true);
}
- /** {@inheritDoc} */
@SuppressWarnings("FutureReturnValueIgnored")
@Override
public void autoGC(ProgressMonitor monitor) {
GC gc = new GC(this);
- gc.setPackConfig(new PackConfig(this));
gc.setProgressMonitor(monitor);
gc.setAuto(true);
gc.setBackground(shouldAutoDetach());
try {
gc.gc();
- } catch (ParseException | IOException e) {
+ } catch (ParseException | IOException | GitAPIException e) {
throw new JGitInternalException(JGitText.get().gcFailed, e);
}
}
@@ -632,16 +633,17 @@ public class FileRepository extends Repository {
* on IO problem
*/
void convertToPackedRefs(boolean writeLogs, boolean backup) throws IOException {
+ File commonDirectory = getCommonDirectory();
List<Ref> all = refs.getRefs();
- File packedRefs = new File(getDirectory(), Constants.PACKED_REFS);
+ File packedRefs = new File(commonDirectory, Constants.PACKED_REFS);
if (packedRefs.exists()) {
throw new IOException(MessageFormat.format(JGitText.get().fileAlreadyExists,
packedRefs.getName()));
}
- File refsFile = new File(getDirectory(), "refs"); //$NON-NLS-1$
+ File refsFile = new File(commonDirectory, "refs"); //$NON-NLS-1$
File refsHeadsFile = new File(refsFile, "heads");//$NON-NLS-1$
- File headFile = new File(getDirectory(), Constants.HEAD);
+ File headFile = new File(commonDirectory, Constants.HEAD);
FileReftableDatabase oldDb = (FileReftableDatabase) refs;
// Remove the dummy files that ensure compatibility with older git
@@ -671,8 +673,8 @@ public class FileRepository extends Repository {
}
if (writeLogs) {
- List<ReflogEntry> logs = oldDb.getReflogReader(r.getName())
- .getReverseEntries();
+ ReflogReader reflogReader = oldDb.getReflogReader(r);
+ List<ReflogEntry> logs = reflogReader.getReverseEntries();
Collections.reverse(logs);
for (ReflogEntry e : logs) {
logWriter.log(r.getName(), e);
@@ -711,12 +713,14 @@ public class FileRepository extends Repository {
}
if (!backup) {
- File reftableDir = new File(getDirectory(), Constants.REFTABLE);
+ File reftableDir = new File(commonDirectory, Constants.REFTABLE);
FileUtils.delete(reftableDir,
FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS);
}
repoConfig.unset(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
ConfigConstants.CONFIG_KEY_REF_STORAGE);
+ repoConfig.setLong(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
repoConfig.save();
}
@@ -740,8 +744,10 @@ public class FileRepository extends Repository {
@SuppressWarnings("nls")
void convertToReftable(boolean writeLogs, boolean backup)
throws IOException {
- File reftableDir = new File(getDirectory(), Constants.REFTABLE);
- File headFile = new File(getDirectory(), Constants.HEAD);
+ File commonDirectory = getCommonDirectory();
+ File directory = getDirectory();
+ File reftableDir = new File(commonDirectory, Constants.REFTABLE);
+ File headFile = new File(directory, Constants.HEAD);
if (reftableDir.exists() && FileUtils.hasFiles(reftableDir.toPath())) {
throw new IOException(JGitText.get().reftableDirExists);
}
@@ -749,28 +755,28 @@ public class FileRepository extends Repository {
// Ignore return value, as it is tied to temporary newRefs file.
FileReftableDatabase.convertFrom(this, writeLogs);
- File refsFile = new File(getDirectory(), "refs");
+ File refsFile = new File(commonDirectory, "refs");
// non-atomic: remove old data.
- File packedRefs = new File(getDirectory(), Constants.PACKED_REFS);
- File logsDir = new File(getDirectory(), Constants.LOGS);
+ File packedRefs = new File(commonDirectory, Constants.PACKED_REFS);
+ File logsDir = new File(commonDirectory, Constants.LOGS);
List<String> additional = getRefDatabase().getAdditionalRefs().stream()
.map(Ref::getName).collect(toList());
additional.add(Constants.HEAD);
if (backup) {
- FileUtils.rename(refsFile, new File(getDirectory(), "refs.old"));
+ FileUtils.rename(refsFile, new File(commonDirectory, "refs.old"));
if (packedRefs.exists()) {
- FileUtils.rename(packedRefs, new File(getDirectory(),
+ FileUtils.rename(packedRefs, new File(commonDirectory,
Constants.PACKED_REFS + ".old"));
}
if (logsDir.exists()) {
FileUtils.rename(logsDir,
- new File(getDirectory(), Constants.LOGS + ".old"));
+ new File(commonDirectory, Constants.LOGS + ".old"));
}
for (String r : additional) {
- FileUtils.rename(new File(getDirectory(), r),
- new File(getDirectory(), r + ".old"));
+ FileUtils.rename(new File(commonDirectory, r),
+ new File(commonDirectory, r + ".old"));
}
} else {
FileUtils.delete(packedRefs, FileUtils.SKIP_MISSING);
@@ -780,7 +786,7 @@ public class FileRepository extends Repository {
FileUtils.delete(refsFile,
FileUtils.RECURSIVE | FileUtils.SKIP_MISSING);
for (String r : additional) {
- new File(getDirectory(), r).delete();
+ new File(commonDirectory, r).delete();
}
}
@@ -794,7 +800,7 @@ public class FileRepository extends Repository {
// Some tools might write directly into .git/refs/heads/BRANCH. By
// putting a file here, this fails spectacularly.
- FileUtils.createNewFile(new File(refsFile, "heads"));
+ FileUtils.createNewFile(new File(refsFile, Constants.HEADS));
repoConfig.setString(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
ConfigConstants.CONFIG_KEY_REF_STORAGE,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
index 6088c152a1..b6bde6eea0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileSnapshot.java
@@ -15,6 +15,7 @@ import static org.eclipse.jgit.util.FS.FileStoreAttributes.FALLBACK_TIMESTAMP_RE
import java.io.File;
import java.io.IOException;
+import java.nio.file.FileSystemException;
import java.nio.file.NoSuchFileException;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Duration;
@@ -22,10 +23,12 @@ import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
+import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FS.FileStoreAttributes;
import org.slf4j.Logger;
@@ -139,29 +142,6 @@ public class FileSnapshot {
* @param modified
* the last modification time of the file
* @return the snapshot.
- * @deprecated use {@link #save(Instant)} instead.
- */
- @Deprecated
- public static FileSnapshot save(long modified) {
- final Instant read = Instant.now();
- return new FileSnapshot(read, Instant.ofEpochMilli(modified),
- UNKNOWN_SIZE, FALLBACK_TIMESTAMP_RESOLUTION, MISSING_FILEKEY);
- }
-
- /**
- * Record a snapshot for a file for which the last modification time is
- * already known.
- * <p>
- * This method should be invoked before the file is accessed.
- * <p>
- * Note that this method cannot rely on measuring file timestamp resolution
- * to avoid racy git issues caused by finite file timestamp resolution since
- * it's unknown in which filesystem the file is located. Hence the worst
- * case fallback for timestamp resolution is used.
- *
- * @param modified
- * the last modification time of the file
- * @return the snapshot.
*/
public static FileSnapshot save(Instant modified) {
final Instant read = Instant.now();
@@ -231,14 +211,8 @@ public class FileSnapshot {
this.useConfig = useConfig;
BasicFileAttributes fileAttributes = null;
try {
- fileAttributes = FS.DETECTED.fileAttributes(file);
- } catch (NoSuchFileException e) {
- this.lastModified = Instant.EPOCH;
- this.size = 0L;
- this.fileKey = MISSING_FILEKEY;
- return;
- } catch (IOException e) {
- LOG.error(e.getMessage(), e);
+ fileAttributes = getFileAttributes(file);
+ } catch (NoSuchElementException e) {
this.lastModified = Instant.EPOCH;
this.size = 0L;
this.fileKey = MISSING_FILEKEY;
@@ -282,23 +256,14 @@ public class FileSnapshot {
* Get time of last snapshot update
*
* @return time of last snapshot update
- * @deprecated use {@link #lastModifiedInstant()} instead
- */
- @Deprecated
- public long lastModified() {
- return lastModified.toEpochMilli();
- }
-
- /**
- * Get time of last snapshot update
- *
- * @return time of last snapshot update
*/
public Instant lastModifiedInstant() {
return lastModified;
}
/**
+ * Get file size in bytes of last snapshot update
+ *
* @return file size in bytes of last snapshot update
*/
public long size() {
@@ -317,16 +282,11 @@ public class FileSnapshot {
long currSize;
Object currFileKey;
try {
- BasicFileAttributes fileAttributes = FS.DETECTED.fileAttributes(path);
+ BasicFileAttributes fileAttributes = getFileAttributes(path);
currLastModified = fileAttributes.lastModifiedTime().toInstant();
currSize = fileAttributes.size();
currFileKey = getFileKey(fileAttributes);
- } catch (NoSuchFileException e) {
- currLastModified = Instant.EPOCH;
- currSize = 0L;
- currFileKey = MISSING_FILEKEY;
- } catch (IOException e) {
- LOG.error(e.getMessage(), e);
+ } catch (NoSuchElementException e) {
currLastModified = Instant.EPOCH;
currSize = 0L;
currFileKey = MISSING_FILEKEY;
@@ -404,7 +364,6 @@ public class FileSnapshot {
&& Objects.equals(fileKey, other.fileKey);
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object obj) {
if (this == obj) {
@@ -420,45 +379,53 @@ public class FileSnapshot {
return equals(other);
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
return Objects.hash(lastModified, Long.valueOf(size), fileKey);
}
/**
- * @return {@code true} if FileSnapshot.isModified(File) found the file size
- * changed
+ * Whether #isModified(File) found the file size changed
+ *
+ * @return {@code true} if #isModified(File) found the file size changed
*/
boolean wasSizeChanged() {
return sizeChanged;
}
/**
- * @return {@code true} if FileSnapshot.isModified(File) found the file key
- * changed
+ * Whether #isModified(File) found the file key changed
+ *
+ * @return {@code true} if #isModified(File) found the file key changed
*/
boolean wasFileKeyChanged() {
return fileKeyChanged;
}
/**
- * @return {@code true} if FileSnapshot.isModified(File) found the file's
- * lastModified changed
+ * Whether #isModified(File) found the file's lastModified changed
+ *
+ * @return {@code true} if #isModified(File) found the file's lastModified
+ * changed
*/
boolean wasLastModifiedChanged() {
return lastModifiedChanged;
}
/**
- * @return {@code true} if FileSnapshot.isModified(File) detected that
- * lastModified is racily clean
+ * Whether #isModified(File) detected that lastModified is racily clean
+ *
+ * @return {@code true} if #isModified(File) detected that lastModified is
+ * racily clean
*/
boolean wasLastModifiedRacilyClean() {
return wasRacyClean;
}
/**
+ * Get the delta in nanoseconds between lastModified and lastRead during
+ * last racy check
+ *
* @return the delta in nanoseconds between lastModified and lastRead during
* last racy check
*/
@@ -467,6 +434,8 @@ public class FileSnapshot {
}
/**
+ * Get the racyLimitNanos threshold in nanoseconds during last racy check
+ *
* @return the racyLimitNanos threshold in nanoseconds during last racy
* check
*/
@@ -474,7 +443,6 @@ public class FileSnapshot {
return racyThreshold;
}
- /** {@inheritDoc} */
@SuppressWarnings({ "nls", "ReferenceEquality" })
@Override
public String toString() {
@@ -576,4 +544,27 @@ public class FileSnapshot {
}
return fileStoreAttributeCache;
}
+
+ private static BasicFileAttributes getFileAttributes(File path)
+ throws NoSuchElementException {
+ try {
+ try {
+ return FS.DETECTED.fileAttributes(path);
+ } catch (IOException e) {
+ if (!FileUtils.isStaleFileHandle(e)) {
+ throw e;
+ }
+ }
+ } catch (NoSuchFileException e) {
+ // ignore
+ } catch (FileSystemException e) {
+ String msg = e.getMessage();
+ if (!msg.endsWith("Not a directory")) { //$NON-NLS-1$
+ LOG.error(msg, e);
+ }
+ } catch (IOException e) {
+ LOG.error(e.getMessage(), e);
+ }
+ throw new NoSuchElementException(path.toString());
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
index be359bbeaa..97473bba2a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
@@ -11,8 +11,10 @@
package org.eclipse.jgit.internal.storage.file;
import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.COMMIT_GRAPH;
import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.KEEP;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.OBJECT_SIZE_INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
import static org.eclipse.jgit.internal.storage.pack.PackExt.REVERSE_INDEX;
@@ -48,7 +50,6 @@ import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -63,6 +64,8 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.api.PackRefsCommand;
+import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.CancelledException;
import org.eclipse.jgit.errors.CorruptObjectException;
@@ -74,6 +77,7 @@ import org.eclipse.jgit.internal.storage.commitgraph.CommitGraphWriter;
import org.eclipse.jgit.internal.storage.commitgraph.GraphCommits;
import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.internal.storage.pack.PackWriter;
+import org.eclipse.jgit.internal.util.ShutdownHook;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig;
@@ -99,7 +103,7 @@ import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FS.LockToken;
import org.eclipse.jgit.util.FileUtils;
-import org.eclipse.jgit.util.GitDateParser;
+import org.eclipse.jgit.util.GitTimeParser;
import org.eclipse.jgit.util.StringUtils;
import org.eclipse.jgit.util.SystemReader;
import org.slf4j.Logger;
@@ -128,12 +132,14 @@ public class GC {
private static final Set<PackExt> PARENT_EXTS = Set.of(PACK, KEEP);
private static final Set<PackExt> CHILD_EXTS = Set.of(BITMAP_INDEX, INDEX,
- REVERSE_INDEX);
+ REVERSE_INDEX, OBJECT_SIZE_INDEX);
private static final int DEFAULT_AUTOPACKLIMIT = 50;
private static final int DEFAULT_AUTOLIMIT = 6700;
+ private static final boolean DEFAULT_WRITE_BLOOM_FILTER = false;
+
private static final boolean DEFAULT_WRITE_COMMIT_GRAPH = false;
private static volatile ExecutorService executor;
@@ -155,11 +161,13 @@ public class GC {
private long expireAgeMillis = -1;
- private Date expire;
+ private Instant expire;
private long packExpireAgeMillis = -1;
- private Date packExpire;
+ private Instant packExpire;
+
+ private Boolean packKeptObjects;
private PackConfig pconfig;
@@ -176,7 +184,7 @@ public class GC {
* prune() to inspect only those reflog entries which have been added since
* last repack().
*/
- private long lastRepackTime;
+ private Instant lastRepackTime;
/**
* Whether gc should do automatic housekeeping
@@ -221,15 +229,18 @@ public class GC {
* gc.log.
*
* @return the collection of
- * {@link org.eclipse.jgit.internal.storage.file.Pack}'s which
- * are newly created
+ * {@link org.eclipse.jgit.internal.storage.file.Pack}'s which are
+ * newly created
* @throws java.io.IOException
+ * if an IO error occurred
* @throws java.text.ParseException
* If the configuration parameter "gc.pruneexpire" couldn't be
* parsed
+ * @throws GitAPIException
+ * If packing refs failed
*/
public CompletableFuture<Collection<Pack>> gc()
- throws IOException, ParseException {
+ throws IOException, ParseException, GitAPIException {
if (!background) {
return CompletableFuture.completedFuture(doGc());
}
@@ -248,7 +259,7 @@ public class GC {
gcLog.commit();
}
return newPacks;
- } catch (IOException | ParseException e) {
+ } catch (IOException | ParseException | GitAPIException e) {
try {
gcLog.write(e.getMessage());
StringWriter sw = new StringWriter();
@@ -271,7 +282,8 @@ public class GC {
return (executor != null) ? executor : WorkQueue.getExecutor();
}
- private Collection<Pack> doGc() throws IOException, ParseException {
+ private Collection<Pack> doGc()
+ throws IOException, ParseException, GitAPIException {
if (automatic && !needGc()) {
return Collections.emptyList();
}
@@ -280,7 +292,8 @@ public class GC {
return Collections.emptyList();
}
pm.start(6 /* tasks */);
- packRefs();
+ new PackRefsCommand(repo).setProgressMonitor(pm).setAll(true)
+ .call();
// TODO: implement reflog_expire(pm, repo);
Collection<Pack> newPacks = repack();
prune(Collections.emptySet());
@@ -297,10 +310,15 @@ public class GC {
* pack files.
*
* @param inserter
+ * used to insert objects
* @param reader
+ * used to read objects
* @param pack
+ * the pack file to loosen objects for
* @param existing
+ * existing objects
* @throws IOException
+ * if an IO error occurred
*/
private void loosen(ObjectDirectoryInserter inserter, ObjectReader reader, Pack pack, HashSet<ObjectId> existing)
throws IOException {
@@ -326,13 +344,17 @@ public class GC {
* directory. If an expirationDate is set then pack files which are younger
* than the expirationDate will not be deleted nor preserved.
* <p>
- * If we're not immediately expiring loose objects, loosen any objects
- * in the old pack files which aren't in the new pack files.
+ * If we're not immediately expiring loose objects, loosen any objects in
+ * the old pack files which aren't in the new pack files.
*
* @param oldPacks
+ * old pack files
* @param newPacks
+ * new pack files
* @throws ParseException
+ * if an error occurred during parsing
* @throws IOException
+ * if an IO error occurred
*/
private void deleteOldPacks(Collection<Pack> oldPacks,
Collection<Pack> newPacks) throws ParseException, IOException {
@@ -350,6 +372,7 @@ public class GC {
prunePreserved();
long packExpireDate = getPackExpireDate();
+ List<PackFile> packFilesToPrune = new ArrayList<>();
oldPackLoop: for (Pack oldPack : oldPacks) {
checkCancelled();
String oldName = oldPack.getPackName();
@@ -367,9 +390,10 @@ public class GC {
loosen(inserter, reader, oldPack, ids);
}
oldPack.close();
- prunePack(oldPack.getPackFile());
+ packFilesToPrune.add(oldPack.getPackFile());
}
}
+ packFilesToPrune.forEach(this::prunePack);
// close the complete object database. That's my only chance to force
// rescanning and to detect that certain pack files are now deleted.
@@ -377,15 +401,22 @@ public class GC {
}
/**
- * Deletes old pack file, unless 'preserve-oldpacks' is set, in which case it
- * moves the pack file to the preserved directory
+ * Deletes old pack file, unless 'preserve-oldpacks' is set, in which case
+ * it moves the pack file to the preserved directory
*
* @param packFile
+ * the packfile to delete
* @param deleteOptions
+ * delete option flags
* @throws IOException
+ * if an IO error occurred
*/
private void removeOldPack(PackFile packFile, int deleteOptions)
throws IOException {
+ if (!packFile.exists()) {
+ return;
+ }
+
if (pconfig.isPreserveOldPacks()) {
File oldPackDir = repo.getObjectDatabase().getPreservedDirectory();
FileUtils.mkdir(oldPackDir, true);
@@ -421,6 +452,7 @@ public class GC {
* with a ".pack" file without a ".index" file.
*
* @param packFile
+ * the pack file to prune files for
*/
private void prunePack(PackFile packFile) {
try {
@@ -448,6 +480,7 @@ public class GC {
* because the filesystem delete operation fails) this is silently ignored.
*
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void prunePacked() throws IOException {
ObjectDirectory objdb = repo.getObjectDatabase();
@@ -506,6 +539,7 @@ public class GC {
* @param objectsToKeep
* a set of objects which should explicitly not be pruned
* @throws java.io.IOException
+ * if an IO error occurred
* @throws java.text.ParseException
* If the configuration parameter "gc.pruneexpire" couldn't be
* parsed
@@ -669,16 +703,18 @@ public class GC {
if (expire == null && expireAgeMillis == -1) {
String pruneExpireStr = getPruneExpireStr();
- if (pruneExpireStr == null)
+ if (pruneExpireStr == null) {
pruneExpireStr = PRUNE_EXPIRE_DEFAULT;
- expire = GitDateParser.parse(pruneExpireStr, null, SystemReader
- .getInstance().getLocale());
+ }
+ expire = GitTimeParser.parseInstant(pruneExpireStr);
expireAgeMillis = -1;
}
- if (expire != null)
- expireDate = expire.getTime();
- if (expireAgeMillis != -1)
+ if (expire != null) {
+ expireDate = expire.toEpochMilli();
+ }
+ if (expireAgeMillis != -1) {
expireDate = System.currentTimeMillis() - expireAgeMillis;
+ }
return expireDate;
}
@@ -695,16 +731,18 @@ public class GC {
String prunePackExpireStr = repo.getConfig().getString(
ConfigConstants.CONFIG_GC_SECTION, null,
ConfigConstants.CONFIG_KEY_PRUNEPACKEXPIRE);
- if (prunePackExpireStr == null)
+ if (prunePackExpireStr == null) {
prunePackExpireStr = PRUNE_PACK_EXPIRE_DEFAULT;
- packExpire = GitDateParser.parse(prunePackExpireStr, null,
- SystemReader.getInstance().getLocale());
+ }
+ packExpire = GitTimeParser.parseInstant(prunePackExpireStr);
packExpireAgeMillis = -1;
}
- if (packExpire != null)
- packExpireDate = packExpire.getTime();
- if (packExpireAgeMillis != -1)
+ if (packExpire != null) {
+ packExpireDate = packExpire.toEpochMilli();
+ }
+ if (packExpireAgeMillis != -1) {
packExpireDate = System.currentTimeMillis() - packExpireAgeMillis;
+ }
return packExpireDate;
}
@@ -713,10 +751,15 @@ public class GC {
* by the given ObjectWalk
*
* @param id2File
+ * mapping objectIds to files
* @param w
+ * used to walk objects
* @throws MissingObjectException
+ * if an object is missing
* @throws IncorrectObjectTypeException
+ * if an object has an unexpected tyoe
* @throws IOException
+ * if an IO error occurred
*/
private void removeReferenced(Map<ObjectId, File> id2File,
ObjectWalk w) throws MissingObjectException,
@@ -752,42 +795,6 @@ public class GC {
}
/**
- * Pack ref storage. For a RefDirectory database, this packs all
- * non-symbolic, loose refs into packed-refs. For Reftable, all of the data
- * is compacted into a single table.
- *
- * @throws java.io.IOException
- */
- public void packRefs() throws IOException {
- RefDatabase refDb = repo.getRefDatabase();
- if (refDb instanceof FileReftableDatabase) {
- // TODO: abstract this more cleanly.
- pm.beginTask(JGitText.get().packRefs, 1);
- try {
- ((FileReftableDatabase) refDb).compactFully();
- } finally {
- pm.endTask();
- }
- return;
- }
-
- Collection<Ref> refs = refDb.getRefsByPrefix(Constants.R_REFS);
- List<String> refsToBePacked = new ArrayList<>(refs.size());
- pm.beginTask(JGitText.get().packRefs, refs.size());
- try {
- for (Ref ref : refs) {
- checkCancelled();
- if (!ref.isSymbolic() && ref.getStorage().isLoose())
- refsToBePacked.add(ref.getName());
- pm.update(1);
- }
- ((RefDirectory) repo.getRefDatabase()).pack(refsToBePacked);
- } finally {
- pm.endTask();
- }
- }
-
- /**
* Packs all objects which reachable from any of the heads into one pack
* file. Additionally all objects which are not reachable from any head but
* which are reachable from any of the other refs (e.g. tags), special refs
@@ -804,14 +811,13 @@ public class GC {
public Collection<Pack> repack() throws IOException {
Collection<Pack> toBeDeleted = repo.getObjectDatabase().getPacks();
- long time = System.currentTimeMillis();
+ Instant time = SystemReader.getInstance().now();
Collection<Ref> refsBefore = getAllRefs();
Set<ObjectId> allHeadsAndTags = new HashSet<>();
Set<ObjectId> allHeads = new HashSet<>();
Set<ObjectId> allTags = new HashSet<>();
Set<ObjectId> nonHeads = new HashSet<>();
- Set<ObjectId> txnHeads = new HashSet<>();
Set<ObjectId> tagTargets = new HashSet<>();
Set<ObjectId> indexObjects = listNonHEADIndexObjects();
@@ -821,7 +827,7 @@ public class GC {
for (Ref ref : refsBefore) {
checkCancelled();
- nonHeads.addAll(listRefLogObjects(ref, 0));
+ nonHeads.addAll(listRefLogObjects(ref, Instant.EPOCH));
if (ref.isSymbolic() || ref.getObjectId() == null) {
continue;
}
@@ -837,11 +843,12 @@ public class GC {
}
}
- List<ObjectIdSet> excluded = new LinkedList<>();
+ List<ObjectIdSet> excluded = new ArrayList<>();
for (Pack p : repo.getObjectDatabase().getPacks()) {
checkCancelled();
- if (p.shouldBeKept())
+ if (!shouldPackKeptObjects() && p.shouldBeKept()) {
excluded.add(p.getIndex());
+ }
}
// Don't exclude tags that are also branch tips
@@ -863,7 +870,7 @@ public class GC {
Pack heads = null;
if (!allHeadsAndTags.isEmpty()) {
heads = writePack(allHeadsAndTags, PackWriter.NONE, allTags,
- refsToExcludeFromBitmap, tagTargets, excluded);
+ refsToExcludeFromBitmap, tagTargets, excluded, true);
if (heads != null) {
ret.add(heads);
excluded.add(0, heads.getIndex());
@@ -871,16 +878,10 @@ public class GC {
}
if (!nonHeads.isEmpty()) {
Pack rest = writePack(nonHeads, allHeadsAndTags, PackWriter.NONE,
- PackWriter.NONE, tagTargets, excluded);
+ PackWriter.NONE, tagTargets, excluded, false);
if (rest != null)
ret.add(rest);
}
- if (!txnHeads.isEmpty()) {
- Pack txn = writePack(txnHeads, PackWriter.NONE, PackWriter.NONE,
- PackWriter.NONE, null, excluded);
- if (txn != null)
- ret.add(txn);
- }
try {
deleteOldPacks(toBeDeleted, ret);
} catch (ParseException e) {
@@ -926,6 +927,7 @@ public class GC {
* the list of wanted objects, writer walks commits starting at
* these. Must not be {@code null}.
* @throws IOException
+ * if an IO error occurred
*/
void writeCommitGraph(@NonNull Set<? extends ObjectId> wants)
throws IOException {
@@ -942,8 +944,10 @@ public class GC {
File tmpFile = null;
try (RevWalk walk = new RevWalk(repo)) {
CommitGraphWriter writer = new CommitGraphWriter(
- GraphCommits.fromWalk(pm, wants, walk));
- tmpFile = File.createTempFile("commit_", ".graph_tmp", //$NON-NLS-1$//$NON-NLS-2$
+ GraphCommits.fromWalk(pm, wants, walk),
+ shouldWriteBloomFilter());
+ tmpFile = File.createTempFile("commit_", //$NON-NLS-1$
+ COMMIT_GRAPH.getTmpExtension(),
repo.getObjectDatabase().getInfoDirectory());
// write the commit-graph file
try (FileOutputStream fos = new FileOutputStream(tmpFile);
@@ -993,7 +997,7 @@ public class GC {
/**
* If {@code true}, will rewrite the commit-graph file when gc is run.
*
- * @return true if commit-graph should be writen. Default is {@code false}.
+ * @return true if commit-graph should be written. Default is {@code false}.
*/
boolean shouldWriteCommitGraphWhenGc() {
return repo.getConfig().getBoolean(ConfigConstants.CONFIG_GC_SECTION,
@@ -1001,6 +1005,17 @@ public class GC {
DEFAULT_WRITE_COMMIT_GRAPH);
}
+ /**
+ * If {@code true}, generates bloom filter in the commit-graph file.
+ *
+ * @return true if bloom filter should be written. Default is {@code false}.
+ */
+ boolean shouldWriteBloomFilter() {
+ return repo.getConfig().getBoolean(ConfigConstants.CONFIG_GC_SECTION,
+ ConfigConstants.CONFIG_KEY_WRITE_CHANGED_PATHS,
+ DEFAULT_WRITE_BLOOM_FILTER);
+ }
+
private static boolean isHead(Ref ref) {
return ref.getName().startsWith(Constants.R_HEADS);
}
@@ -1010,7 +1025,7 @@ public class GC {
}
private void deleteEmptyRefsFolders() throws IOException {
- Path refs = repo.getDirectory().toPath().resolve(Constants.R_REFS);
+ Path refs = repo.getCommonDirectory().toPath().resolve(Constants.R_REFS);
// Avoid deleting a folder that was created after the threshold so that concurrent
// operations trying to create a reference are not impacted
Instant threshold = Instant.now().minus(30, ChronoUnit.SECONDS);
@@ -1141,20 +1156,24 @@ public class GC {
/**
* @param ref
* the ref which log should be inspected
- * @param minTime only reflog entries not older then this time are processed
+ * @param minTime
+ * only reflog entries equal or younger than this time are
+ * processed
* @return the {@link ObjectId}s contained in the reflog
* @throws IOException
+ * if an IO error occurred
*/
- private Set<ObjectId> listRefLogObjects(Ref ref, long minTime) throws IOException {
- ReflogReader reflogReader = repo.getReflogReader(ref);
+ private Set<ObjectId> listRefLogObjects(Ref ref, Instant minTime) throws IOException {
+ ReflogReader reflogReader = repo.getRefDatabase().getReflogReader(ref);
List<ReflogEntry> rlEntries = reflogReader
.getReverseEntries();
if (rlEntries == null || rlEntries.isEmpty())
return Collections.emptySet();
Set<ObjectId> ret = new HashSet<>();
for (ReflogEntry e : rlEntries) {
- if (e.getWho().getWhen().getTime() < minTime)
+ if (e.getWho().getWhenAsInstant().isBefore(minTime)) {
break;
+ }
ObjectId newId = e.getNewId();
if (newId != null && !ObjectId.zeroId().equals(newId))
ret.add(newId);
@@ -1175,6 +1194,7 @@ public class GC {
*
* @return a collection of refs pointing to live objects.
* @throws IOException
+ * if an IO error occurred
*/
private Collection<Ref> getAllRefs() throws IOException {
RefDatabase refdb = repo.getRefDatabase();
@@ -1201,8 +1221,11 @@ public class GC {
*
* @return a set of ObjectIds of changed objects in the index
* @throws IOException
+ * if an IO error occurred
* @throws CorruptObjectException
+ * if an object is corrupt
* @throws NoWorkTreeException
+ * if the repository has no working directory
*/
private Set<ObjectId> listNonHEADIndexObjects()
throws CorruptObjectException, IOException {
@@ -1251,7 +1274,7 @@ public class GC {
private Pack writePack(@NonNull Set<? extends ObjectId> want,
@NonNull Set<? extends ObjectId> have, @NonNull Set<ObjectId> tags,
@NonNull Set<ObjectId> excludedRefsTips,
- Set<ObjectId> tagTargets, List<ObjectIdSet> excludeObjects)
+ Set<ObjectId> tagTargets, List<ObjectIdSet> excludeObjects, boolean createBitmap)
throws IOException {
checkCancelled();
File tmpPack = null;
@@ -1282,6 +1305,7 @@ public class GC {
if (excludeObjects != null)
for (ObjectIdSet idx : excludeObjects)
pw.excludeObjects(idx);
+ pw.setCreateBitmaps(createBitmap);
pw.preparePack(pm, want, have, PackWriter.NONE,
union(tags, excludedRefsTips));
if (pw.getObjectCount() == 0)
@@ -1292,10 +1316,11 @@ public class GC {
ObjectId id = pw.computeName();
File packdir = repo.getObjectDatabase().getPackDirectory();
packdir.mkdirs();
- tmpPack = File.createTempFile("gc_", ".pack_tmp", packdir); //$NON-NLS-1$ //$NON-NLS-2$
- final String tmpBase = tmpPack.getName()
+ tmpPack = File.createTempFile("gc_", //$NON-NLS-1$
+ PACK.getTmpExtension(), packdir);
+ String tmpBase = tmpPack.getName()
.substring(0, tmpPack.getName().lastIndexOf('.'));
- File tmpIdx = new File(packdir, tmpBase + ".idx_tmp"); //$NON-NLS-1$
+ File tmpIdx = new File(packdir, tmpBase + INDEX.getTmpExtension());
tmpExts.put(INDEX, tmpIdx);
if (!tmpIdx.createNewFile())
@@ -1320,8 +1345,42 @@ public class GC {
idxChannel.force(true);
}
+
+ if (pw.isReverseIndexEnabled()) {
+ File tmpReverseIndexFile = new File(packdir,
+ tmpBase + REVERSE_INDEX.getTmpExtension());
+ tmpExts.put(REVERSE_INDEX, tmpReverseIndexFile);
+ if (!tmpReverseIndexFile.createNewFile()) {
+ throw new IOException(MessageFormat.format(
+ JGitText.get().cannotCreateIndexfile,
+ tmpReverseIndexFile.getPath()));
+ }
+ try (FileOutputStream fos = new FileOutputStream(
+ tmpReverseIndexFile);
+ FileChannel channel = fos.getChannel();
+ OutputStream stream = Channels
+ .newOutputStream(channel)) {
+ pw.writeReverseIndex(stream);
+ channel.force(true);
+ }
+ }
+
+ // write the object size
+ if (pconfig.isWriteObjSizeIndex()) {
+ File tmpSizeIdx = new File(packdir, tmpBase + ".objsize_tmp"); //$NON-NLS-1$
+ tmpExts.put(OBJECT_SIZE_INDEX, tmpSizeIdx);
+ try (FileOutputStream fos = new FileOutputStream(tmpSizeIdx);
+ FileChannel idxChannel = fos.getChannel();
+ OutputStream idxStream = Channels
+ .newOutputStream(idxChannel)) {
+ pw.writeObjectSizeIndex(idxStream);
+ idxChannel.force(true);
+ }
+ }
+
if (pw.prepareBitmapIndex(pm)) {
- File tmpBitmapIdx = new File(packdir, tmpBase + ".bitmap_tmp"); //$NON-NLS-1$
+ File tmpBitmapIdx = new File(packdir,
+ tmpBase + BITMAP_INDEX.getTmpExtension());
tmpExts.put(BITMAP_INDEX, tmpBitmapIdx);
if (!tmpBitmapIdx.createNewFile())
@@ -1333,7 +1392,7 @@ public class GC {
FileChannel idxChannel = fos.getChannel();
OutputStream idxStream = Channels
.newOutputStream(idxChannel)) {
- pw.writeBitmapIndex(idxStream);
+ pw.writeBitmapIndex(new PackBitmapIndexWriterV1(idxStream));
idxChannel.force(true);
}
}
@@ -1412,6 +1471,21 @@ public class GC {
}
/**
+ * Define whether to include objects in `.keep` files when repacking.
+ *
+ * @param packKeptObjects Whether to include objects in `.keep` files when repacking.
+ */
+ public void setPackKeptObjects(boolean packKeptObjects) {
+ this.packKeptObjects = Boolean.valueOf(packKeptObjects);
+ }
+
+ @SuppressWarnings("boxing")
+ private boolean shouldPackKeptObjects() {
+ return Optional.ofNullable(packKeptObjects)
+ .orElse(pconfig.isPackKeptObjects());
+ }
+
+ /**
* A class holding statistical data for a FileRepository regarding how many
* objects are stored as loose or packed objects
*/
@@ -1429,6 +1503,18 @@ public class GC {
public long numberOfPackFiles;
/**
+ * The number of pack files that were created since the last bitmap
+ * generation.
+ */
+ public long numberOfPackFilesSinceBitmap;
+
+ /**
+ * The number of objects stored in pack files and as loose object
+ * created after the last bitmap generation.
+ */
+ public long numberOfObjectsSinceBitmap;
+
+ /**
* The number of objects stored as loose objects.
*/
public long numberOfLooseObjects;
@@ -1458,17 +1544,28 @@ public class GC {
*/
public long numberOfBitmaps;
+ /**
+ * The number of objects in the size-index of the packs
+ */
+ public long numberOfSizeIndexedObjects;
+
@Override
public String toString() {
final StringBuilder b = new StringBuilder();
b.append("numberOfPackedObjects=").append(numberOfPackedObjects); //$NON-NLS-1$
b.append(", numberOfPackFiles=").append(numberOfPackFiles); //$NON-NLS-1$
+ b.append(", numberOfPackFilesSinceBitmap=") //$NON-NLS-1$
+ .append(numberOfPackFilesSinceBitmap);
+ b.append(", numberOfObjectsSinceBitmap=") //$NON-NLS-1$
+ .append(numberOfObjectsSinceBitmap);
b.append(", numberOfLooseObjects=").append(numberOfLooseObjects); //$NON-NLS-1$
b.append(", numberOfLooseRefs=").append(numberOfLooseRefs); //$NON-NLS-1$
b.append(", numberOfPackedRefs=").append(numberOfPackedRefs); //$NON-NLS-1$
b.append(", sizeOfLooseObjects=").append(sizeOfLooseObjects); //$NON-NLS-1$
b.append(", sizeOfPackedObjects=").append(sizeOfPackedObjects); //$NON-NLS-1$
b.append(", numberOfBitmaps=").append(numberOfBitmaps); //$NON-NLS-1$
+ b.append(", numberOfSizeIndexedObjects=") //$NON-NLS-1$
+ .append(numberOfSizeIndexedObjects);
return b.toString();
}
}
@@ -1477,17 +1574,28 @@ public class GC {
* Returns information about objects and pack files for a FileRepository.
*
* @return information about objects and pack files for a FileRepository
- * @throws java.io.IOException
+ * @throws java.io.IOException
+ * if an IO error occurred
*/
public RepoStatistics getStatistics() throws IOException {
RepoStatistics ret = new RepoStatistics();
Collection<Pack> packs = repo.getObjectDatabase().getPacks();
+ long latestBitmapTime = 0L;
for (Pack p : packs) {
- ret.numberOfPackedObjects += p.getIndex().getObjectCount();
+ long packedObjects = p.getIndex().getObjectCount();
+ ret.numberOfPackedObjects += packedObjects;
ret.numberOfPackFiles++;
ret.sizeOfPackedObjects += p.getPackFile().length();
- if (p.getBitmapIndex() != null)
+ ret.numberOfSizeIndexedObjects += p.getObjectSizeIndexCount();
+ if (p.getBitmapIndex() != null) {
ret.numberOfBitmaps += p.getBitmapIndex().getBitmapCount();
+ if (latestBitmapTime == 0L) {
+ latestBitmapTime = p.getFileSnapshot().lastModifiedInstant().toEpochMilli();
+ }
+ } else if (latestBitmapTime == 0L) {
+ ret.numberOfPackFilesSinceBitmap++;
+ ret.numberOfObjectsSinceBitmap += packedObjects;
+ }
}
File objDir = repo.getObjectsDirectory();
String[] fanout = objDir.list();
@@ -1503,6 +1611,9 @@ public class GC {
continue;
ret.numberOfLooseObjects++;
ret.sizeOfLooseObjects += f.length();
+ if (f.lastModified() > latestBitmapTime) {
+ ret.numberOfObjectsSinceBitmap ++;
+ }
}
}
}
@@ -1578,12 +1689,31 @@ public class GC {
* candidate for pruning.
*
* @param expire
- * instant in time which defines object expiration
- * objects with modification time before this instant are expired
- * objects with modification time newer or equal to this instant
- * are not expired
+ * instant in time which defines object expiration objects with
+ * modification time before this instant are expired objects with
+ * modification time newer or equal to this instant are not
+ * expired
+ * @deprecated use {@link #setExpire(Instant)} instead
*/
+ @Deprecated(since = "7.2")
public void setExpire(Date expire) {
+ this.expire = expire.toInstant();
+ expireAgeMillis = -1;
+ }
+
+ /**
+ * During gc() or prune() each unreferenced, loose object which has been
+ * created or modified after or at <code>expire</code> will not be pruned.
+ * Only older objects may be pruned. If set to null then every object is a
+ * candidate for pruning.
+ *
+ * @param expire
+ * instant in time which defines object expiration objects with
+ * modification time before this instant are expired objects with
+ * modification time newer or equal to this instant are not
+ * expired
+ */
+ public void setExpire(Instant expire) {
this.expire = expire;
expireAgeMillis = -1;
}
@@ -1596,8 +1726,24 @@ public class GC {
*
* @param packExpire
* instant in time which defines packfile expiration
+ * @deprecated use {@link #setPackExpire(Instant)} instead
*/
+ @Deprecated(since = "7.2")
public void setPackExpire(Date packExpire) {
+ this.packExpire = packExpire.toInstant();
+ packExpireAgeMillis = -1;
+ }
+
+ /**
+ * During gc() or prune() packfiles which are created or modified after or
+ * at <code>packExpire</code> will not be deleted. Only older packfiles may
+ * be deleted. If set to null then every packfile is a candidate for
+ * deletion.
+ *
+ * @param packExpire
+ * instant in time which defines packfile expiration
+ */
+ public void setPackExpire(Instant packExpire) {
this.packExpire = packExpire;
packExpireAgeMillis = -1;
}
@@ -1665,6 +1811,8 @@ public class GC {
}
/**
+ * Whether number of packs exceeds gc.autopacklimit
+ *
* @return {@code true} if number of packs &gt; gc.autopacklimit (default
* 50)
*/
@@ -1736,6 +1884,8 @@ public class GC {
private FileChannel channel;
+ private ShutdownHook.Listener shutdownListener = this::close;
+
PidLock() {
pidFile = repo.getDirectory().toPath().resolve(GC_PID);
}
@@ -1764,12 +1914,7 @@ public class GC {
}
channel.write(ByteBuffer
.wrap(getProcDesc().getBytes(StandardCharsets.UTF_8)));
- Thread cleanupHook = new Thread(() -> close());
- try {
- Runtime.getRuntime().addShutdownHook(cleanupHook);
- } catch (IllegalStateException e) {
- // ignore - the VM is already shutting down
- }
+ ShutdownHook.INSTANCE.register(shutdownListener);
} catch (IOException | OverlappingFileLockException e) {
try {
failedToLock();
@@ -1816,7 +1961,7 @@ public class GC {
}
private String getProcDesc() {
- StringBuffer s = new StringBuffer(Long.toString(getPID()));
+ StringBuilder s = new StringBuilder(Long.toString(getPID()));
s.append(' ');
s.append(getHostName());
return s.toString();
@@ -1838,6 +1983,7 @@ public class GC {
public void close() {
boolean wasLocked = false;
try {
+ ShutdownHook.INSTANCE.unregister(shutdownListener);
if (lock != null && lock.isValid()) {
lock.release();
wasLocked = true;
@@ -1852,7 +1998,7 @@ public class GC {
token.close();
}
if (wasLocked) {
- FileUtils.delete(pidFile.toFile(), FileUtils.RETRY);
+ FileUtils.delete(pidFile.toFile(), FileUtils.RETRY | FileUtils.SKIP_MISSING);
}
} catch (IOException e) {
LOG.error(MessageFormat
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
index 86994a9eeb..862aaab0ee 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java
@@ -23,8 +23,7 @@ import java.time.Instant;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.util.FileUtils;
-import org.eclipse.jgit.util.GitDateParser;
-import org.eclipse.jgit.util.SystemReader;
+import org.eclipse.jgit.util.GitTimeParser;
/**
* This class manages the gc.log file for a {@link FileRepository}.
@@ -50,7 +49,7 @@ class GcLog {
*/
GcLog(FileRepository repo) {
this.repo = repo;
- logFile = new File(repo.getDirectory(), "gc.log"); //$NON-NLS-1$
+ logFile = new File(repo.getCommonDirectory(), "gc.log"); //$NON-NLS-1$
lock = new LockFile(logFile);
}
@@ -62,8 +61,7 @@ class GcLog {
if (logExpiryStr == null) {
logExpiryStr = LOG_EXPIRY_DEFAULT;
}
- gcLogExpire = GitDateParser.parse(logExpiryStr, null,
- SystemReader.getInstance().getLocale()).toInstant();
+ gcLogExpire = GitTimeParser.parseInstant(logExpiryStr);
}
return gcLogExpire;
}
@@ -132,6 +130,7 @@ class GcLog {
* @param content
* The content to write
* @throws IOException
+ * if an IO error occurred
*/
void write(String content) throws IOException {
if (content.length() > 0) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java
index 254557c08f..bbb7476d65 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java
@@ -39,6 +39,7 @@ public class GlobalAttributesNode extends AttributesNode {
*
* @return the attributes node
* @throws java.io.IOException
+ * if an IO error occurred
*/
public AttributesNode load() throws IOException {
AttributesNode r = new AttributesNode();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
index a22cd3ffeb..e8d442b8fb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java
@@ -39,13 +39,14 @@ public class InfoAttributesNode extends AttributesNode {
*
* @return the attributes node
* @throws java.io.IOException
+ * if an IO error occurred
*/
public AttributesNode load() throws IOException {
AttributesNode r = new AttributesNode();
FS fs = repository.getFS();
- File attributes = fs.resolve(repository.getDirectory(),
+ File attributes = fs.resolve(repository.getCommonDirectory(),
Constants.INFO_ATTRIBUTES);
FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributes);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LargePackedWholeObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LargePackedWholeObject.java
index e2fbd7a0b4..15223ad437 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LargePackedWholeObject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LargePackedWholeObject.java
@@ -44,25 +44,21 @@ class LargePackedWholeObject extends ObjectLoader {
this.db = db;
}
- /** {@inheritDoc} */
@Override
public int getType() {
return type;
}
- /** {@inheritDoc} */
@Override
public long getSize() {
return size;
}
- /** {@inheritDoc} */
@Override
public boolean isLarge() {
return true;
}
- /** {@inheritDoc} */
@Override
public byte[] getCachedBytes() throws LargeObjectException {
try {
@@ -72,7 +68,6 @@ class LargePackedWholeObject extends ObjectLoader {
}
}
- /** {@inheritDoc} */
@Override
public ObjectStream openStream() throws MissingObjectException, IOException {
WindowCursor wc = new WindowCursor(db);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java
index 2d73f9bd10..d0d320bf02 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java
@@ -41,7 +41,6 @@ public class LazyObjectIdSetFile implements ObjectIdSet {
this.src = src;
}
- /** {@inheritDoc} */
@Override
public boolean contains(AnyObjectId objectId) {
if (set == null) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java
index f112947bae..22eef9468c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java
@@ -39,7 +39,6 @@ class LocalCachedPack extends CachedPack {
this.packs = packs.toArray(new Pack[0]);
}
- /** {@inheritDoc} */
@Override
public long getObjectCount() throws IOException {
long cnt = 0;
@@ -54,7 +53,6 @@ class LocalCachedPack extends CachedPack {
pack.copyPackAsIs(out, wc);
}
- /** {@inheritDoc} */
@Override
public boolean hasObject(ObjectToPack obj, StoredObjectRepresentation rep) {
try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectRepresentation.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectRepresentation.java
index 559718af3a..af571622b4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectRepresentation.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectRepresentation.java
@@ -22,6 +22,11 @@ class LocalObjectRepresentation extends StoredObjectRepresentation {
public int getFormat() {
return PACK_WHOLE;
}
+
+ @Override
+ public boolean wasDeltaAttempted() {
+ return true;
+ }
};
r.pack = pack;
r.offset = offset;
@@ -59,13 +64,11 @@ class LocalObjectRepresentation extends StoredObjectRepresentation {
private ObjectId baseId;
- /** {@inheritDoc} */
@Override
public int getWeight() {
return (int) Math.min(length, Integer.MAX_VALUE);
}
- /** {@inheritDoc} */
@Override
public ObjectId getDeltaBase() {
if (baseId == null && getFormat() == PACK_DELTA) {
@@ -83,5 +86,10 @@ class LocalObjectRepresentation extends StoredObjectRepresentation {
public int getFormat() {
return PACK_DELTA;
}
+
+ @Override
+ public boolean wasDeltaAttempted() {
+ return true;
+ }
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectToPack.java
index ac6cd212d5..ca25212e00 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectToPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalObjectToPack.java
@@ -29,14 +29,12 @@ class LocalObjectToPack extends ObjectToPack {
super(src, type);
}
- /** {@inheritDoc} */
@Override
protected void clearReuseAsIs() {
super.clearReuseAsIs();
pack = null;
}
- /** {@inheritDoc} */
@Override
public void select(StoredObjectRepresentation ref) {
LocalObjectRepresentation ptr = (LocalObjectRepresentation) ref;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
index d06b5a72d1..9e12ee8a0c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java
@@ -24,6 +24,7 @@ import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime;
import java.text.MessageFormat;
@@ -31,6 +32,7 @@ import java.time.Instant;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.util.ShutdownHook;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.util.FS;
@@ -79,6 +81,7 @@ public class LockFile {
* Get the lock file corresponding to the given file.
*
* @param file
+ * given file
* @return lock file
*/
static File getLockFile(File file) {
@@ -112,6 +115,8 @@ public class LockFile {
private LockToken token;
+ private ShutdownHook.Listener shutdownListener = this::unlock;
+
/**
* Create a new lock for any file.
*
@@ -137,15 +142,15 @@ public class LockFile {
throw new IllegalStateException(
MessageFormat.format(JGitText.get().lockAlreadyHeld, ref));
}
- FileUtils.mkdirs(lck.getParentFile(), true);
try {
- token = FS.DETECTED.createNewFileAtomic(lck);
+ token = createLockFileWithRetry();
} catch (IOException e) {
LOG.error(JGitText.get().failedCreateLockFile, lck, e);
throw e;
}
boolean obtainedLock = token.isCreated();
if (obtainedLock) {
+ ShutdownHook.INSTANCE.register(shutdownListener);
haveLck = true;
isAppend = false;
written = false;
@@ -155,6 +160,19 @@ public class LockFile {
return obtainedLock;
}
+ private FS.LockToken createLockFileWithRetry() throws IOException {
+ try {
+ return createLockFile();
+ } catch (NoSuchFileException e) {
+ return createLockFile();
+ }
+ }
+
+ private FS.LockToken createLockFile() throws IOException {
+ FileUtils.mkdirs(lck.getParentFile(), true);
+ return FS.DETECTED.createNewFileAtomic(lck);
+ }
+
/**
* Try to establish the lock for appending.
*
@@ -470,6 +488,7 @@ public class LockFile {
* the lock is not held.
*/
public boolean commit() {
+ ShutdownHook.INSTANCE.unregister(shutdownListener);
if (os != null) {
unlock();
throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotClosed, ref));
@@ -509,17 +528,6 @@ public class LockFile {
* Get the modification time of the output file when it was committed.
*
* @return modification time of the lock file right before we committed it.
- * @deprecated use {@link #getCommitLastModifiedInstant()} instead
- */
- @Deprecated
- public long getCommitLastModified() {
- return commitSnapshot.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 Instant getCommitLastModifiedInstant() {
return commitSnapshot.lastModifiedInstant();
@@ -550,6 +558,7 @@ public class LockFile {
* The temporary file (if created) is deleted before returning.
*/
public void unlock() {
+ ShutdownHook.INSTANCE.unregister(shutdownListener);
if (os != null) {
try {
os.close();
@@ -575,7 +584,6 @@ public class LockFile {
written = false;
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java
index 326c5f6457..909b3e3082 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LooseObjects.java
@@ -26,8 +26,9 @@ import org.eclipse.jgit.internal.storage.file.FileObjectDatabase.InsertLooseObje
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.CoreConfig.TrustStat;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.util.FileUtils;
@@ -49,13 +50,13 @@ class LooseObjects {
* Maximum number of attempts to read a loose object for which a stale file
* handle exception is thrown
*/
- private final static int MAX_LOOSE_OBJECT_STALE_READ_ATTEMPTS = 5;
+ private final static int MAX_STALE_READ_RETRIES = 5;
private final File directory;
private final UnpackedObjectCache unpackedObjectCache;
- private final boolean trustFolderStat;
+ private final TrustStat trustLooseObjectStat;
/**
* Initialize a reference to an on-disk object directory.
@@ -68,9 +69,8 @@ class LooseObjects {
LooseObjects(Config config, File dir) {
directory = dir;
unpackedObjectCache = new UnpackedObjectCache();
- trustFolderStat = config.getBoolean(
- ConfigConstants.CONFIG_CORE_SECTION,
- ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
+ trustLooseObjectStat = config.get(CoreConfig.KEY)
+ .getTrustLooseObjectStat();
}
/**
@@ -90,7 +90,6 @@ class LooseObjects {
unpackedObjectCache().clear();
}
- /** {@inheritDoc} */
@Override
public String toString() {
return "LooseObjects[" + directory + "]"; //$NON-NLS-1$ //$NON-NLS-2$
@@ -109,7 +108,8 @@ class LooseObjects {
*/
boolean has(AnyObjectId objectId) {
boolean exists = hasWithoutRefresh(objectId);
- if (trustFolderStat || exists) {
+ if (trustLooseObjectStat == TrustStat.ALWAYS
+ || exists) {
return exists;
}
try (InputStream stream = Files.newInputStream(directory.toPath())) {
@@ -164,10 +164,31 @@ class LooseObjects {
}
ObjectLoader open(WindowCursor curs, AnyObjectId id) throws IOException {
- int readAttempts = 0;
- while (readAttempts < MAX_LOOSE_OBJECT_STALE_READ_ATTEMPTS) {
- readAttempts++;
- File path = fileFor(id);
+ File path = fileFor(id);
+ for (int retries = 0; retries < MAX_STALE_READ_RETRIES; retries++) {
+ boolean reload = true;
+ switch (trustLooseObjectStat) {
+ case NEVER:
+ break;
+ case AFTER_OPEN:
+ try (InputStream stream = Files
+ .newInputStream(path.getParentFile().toPath())) {
+ // open the loose object's fanout directory to refresh
+ // attributes (on some NFS clients)
+ } catch (FileNotFoundException | NoSuchFileException e) {
+ // ignore
+ }
+ //$FALL-THROUGH$
+ case ALWAYS:
+ if (!path.exists()) {
+ reload = false;
+ }
+ break;
+ case INHERIT:
+ // only used in CoreConfig internally
+ throw new IllegalStateException();
+ }
+ if (reload) {
try {
return getObjectLoader(curs, path, id);
} catch (FileNotFoundException noFile) {
@@ -181,9 +202,10 @@ class LooseObjects {
}
if (LOG.isDebugEnabled()) {
LOG.debug(MessageFormat.format(
- JGitText.get().looseObjectHandleIsStale, id.name(),
- Integer.valueOf(readAttempts), Integer.valueOf(
- MAX_LOOSE_OBJECT_STALE_READ_ATTEMPTS)));
+ JGitText.get().looseObjectHandleIsStale,
+ id.name(), Integer.valueOf(retries),
+ Integer.valueOf(MAX_STALE_READ_RETRIES)));
+ }
}
}
}
@@ -209,7 +231,7 @@ class LooseObjects {
try {
return getObjectLoaderWithoutRefresh(curs, path, id);
} catch (FileNotFoundException e) {
- if (trustFolderStat) {
+ if (trustLooseObjectStat == TrustStat.ALWAYS) {
throw e;
}
try (InputStream stream = Files
@@ -246,7 +268,7 @@ class LooseObjects {
return getSizeWithoutRefresh(curs, id);
} catch (FileNotFoundException noFile) {
try {
- if (trustFolderStat) {
+ if (trustLooseObjectStat == TrustStat.ALWAYS) {
throw noFile;
}
try (InputStream stream = Files
@@ -280,7 +302,7 @@ class LooseObjects {
// that already exists. We can't be sure renameTo() would
// fail on all platforms if dst exists, so we check first.
//
- FileUtils.delete(tmp, FileUtils.RETRY);
+ FileUtils.delete(tmp, FileUtils.RETRY | FileUtils.SKIP_MISSING);
return InsertLooseObjectResult.EXISTS_LOOSE;
}
@@ -298,7 +320,7 @@ class LooseObjects {
// Any other IO error is considered a failure.
//
LOG.error(e.getMessage(), e);
- FileUtils.delete(tmp, FileUtils.RETRY);
+ FileUtils.delete(tmp, FileUtils.RETRY | FileUtils.SKIP_MISSING);
return InsertLooseObjectResult.FAILURE;
}
@@ -310,7 +332,7 @@ class LooseObjects {
// know what went wrong, so fail.
//
LOG.error(e.getMessage(), e);
- FileUtils.delete(tmp, FileUtils.RETRY);
+ FileUtils.delete(tmp, FileUtils.RETRY | FileUtils.SKIP_MISSING);
return InsertLooseObjectResult.FAILURE;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
index f27daad897..af1671d442 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
@@ -70,6 +70,11 @@ import org.eclipse.jgit.util.FileUtils;
* considered.
*/
public class ObjectDirectory extends FileObjectDatabase {
+ @FunctionalInterface
+ private interface TriFunctionThrowsException<A, A2, A3, R, E extends Exception> {
+ R apply(A a, A2 a2, A3 a3) throws E;
+ }
+
/** Maximum number of candidates offered as resolutions of abbreviation. */
private static final int RESOLVE_ABBREV_LIMIT = 256;
@@ -145,7 +150,6 @@ public class ObjectDirectory extends FileObjectDatabase {
}
}
- /** {@inheritDoc} */
@Override
public final File getDirectory() {
return loose.getDirectory();
@@ -169,13 +173,11 @@ public class ObjectDirectory extends FileObjectDatabase {
return preserved.getDirectory();
}
- /** {@inheritDoc} */
@Override
public boolean exists() {
return fs.exists(objects);
}
- /** {@inheritDoc} */
@Override
public void create() throws IOException {
loose.create();
@@ -183,7 +185,6 @@ public class ObjectDirectory extends FileObjectDatabase {
packed.create();
}
- /** {@inheritDoc} */
@Override
public ObjectDirectoryInserter newInserter() {
return new ObjectDirectoryInserter(this, config);
@@ -199,12 +200,12 @@ public class ObjectDirectory extends FileObjectDatabase {
return new PackInserter(this);
}
- /** {@inheritDoc} */
@Override
public void close() {
loose.close();
packed.close();
+ preserved.close();
// Fully close all loaded alternates and clear the alternate list.
AlternateHandle[] alt = alternates.get();
@@ -214,13 +215,11 @@ public class ObjectDirectory extends FileObjectDatabase {
}
}
- /** {@inheritDoc} */
@Override
public Collection<Pack> getPacks() {
return packed.getPacks();
}
- /** {@inheritDoc} */
@Override
public long getApproximateObjectCount() {
long count = 0;
@@ -234,7 +233,6 @@ public class ObjectDirectory extends FileObjectDatabase {
return count;
}
- /** {@inheritDoc} */
@Override
public Optional<CommitGraph> getCommitGraph() {
if (config.get(CoreConfig.KEY).enableCommitGraph()) {
@@ -244,7 +242,6 @@ public class ObjectDirectory extends FileObjectDatabase {
}
/**
- * {@inheritDoc}
* <p>
* Add a single existing pack to the list of available pack files.
*/
@@ -268,18 +265,16 @@ public class ObjectDirectory extends FileObjectDatabase {
}
PackFile bitmapIdx = pf.create(BITMAP_INDEX);
- Pack res = new Pack(pack, bitmapIdx.exists() ? bitmapIdx : null);
+ Pack res = new Pack(config, pack, bitmapIdx.exists() ? bitmapIdx : null);
packed.insert(res);
return res;
}
- /** {@inheritDoc} */
@Override
public String toString() {
return "ObjectDirectory[" + getDirectory() + "]"; //$NON-NLS-1$ //$NON-NLS-2$
}
- /** {@inheritDoc} */
@Override
public boolean has(AnyObjectId objectId) {
return loose.hasCached(objectId)
@@ -358,9 +353,13 @@ public class ObjectDirectory extends FileObjectDatabase {
@Override
ObjectLoader openObject(WindowCursor curs, AnyObjectId objectId)
throws IOException {
- ObjectLoader ldr = openObjectWithoutRestoring(curs, objectId);
- if (ldr == null && restoreFromSelfOrAlternate(objectId, null)) {
+ ObjectLoader ldr = getFromLocalObjectToPack(curs, objectId,
+ (p, c, l) -> p.load(c, l.offset));
+ if (ldr == null) {
ldr = openObjectWithoutRestoring(curs, objectId);
+ if (ldr == null && restoreFromSelfOrAlternate(objectId, null)) {
+ ldr = openObjectWithoutRestoring(curs, objectId);
+ }
}
return ldr;
}
@@ -429,11 +428,16 @@ public class ObjectDirectory extends FileObjectDatabase {
return loose.open(curs, id);
}
+ @SuppressWarnings("boxing")
@Override
long getObjectSize(WindowCursor curs, AnyObjectId id) throws IOException {
- long sz = getObjectSizeWithoutRestoring(curs, id);
- if (0 > sz && restoreFromSelfOrAlternate(id, null)) {
+ Long sz = getFromLocalObjectToPack(curs, id,
+ (p, c, l) -> p.getObjectSize(c, l));
+ if (sz == null) {
sz = getObjectSizeWithoutRestoring(curs, id);
+ if (sz < 0 && restoreFromSelfOrAlternate(id, null)) {
+ sz = getObjectSizeWithoutRestoring(curs, id);
+ }
}
return sz;
}
@@ -442,12 +446,12 @@ public class ObjectDirectory extends FileObjectDatabase {
AnyObjectId id) throws IOException {
if (loose.hasCached(id)) {
long len = loose.getSize(curs, id);
- if (0 <= len) {
+ if (len >= 0) {
return len;
}
}
long len = getPackedSizeFromSelfOrAlternate(curs, id, null);
- if (0 <= len) {
+ if (len >= 0) {
return len;
}
return getLooseSizeFromSelfOrAlternate(curs, id, null);
@@ -457,14 +461,14 @@ public class ObjectDirectory extends FileObjectDatabase {
AnyObjectId id, Set<AlternateHandle.Id> skips)
throws PackMismatchException {
long len = packed.getSize(curs, id);
- if (0 <= len) {
+ if (len >= 0) {
return len;
}
skips = addMe(skips);
for (AlternateHandle alt : myAlternates()) {
if (!skips.contains(alt.getId())) {
len = alt.db.getPackedSizeFromSelfOrAlternate(curs, id, skips);
- if (0 <= len) {
+ if (len >= 0) {
return len;
}
}
@@ -475,14 +479,14 @@ public class ObjectDirectory extends FileObjectDatabase {
private long getLooseSizeFromSelfOrAlternate(WindowCursor curs,
AnyObjectId id, Set<AlternateHandle.Id> skips) throws IOException {
long len = loose.getSize(curs, id);
- if (0 <= len) {
+ if (len >= 0) {
return len;
}
skips = addMe(skips);
for (AlternateHandle alt : myAlternates()) {
if (!skips.contains(alt.getId())) {
len = alt.db.getLooseSizeFromSelfOrAlternate(curs, id, skips);
- if (0 <= len) {
+ if (len >= 0) {
return len;
}
}
@@ -490,6 +494,24 @@ public class ObjectDirectory extends FileObjectDatabase {
return -1;
}
+ private <R> R getFromLocalObjectToPack(WindowCursor curs,
+ AnyObjectId objectId,
+ TriFunctionThrowsException<Pack, WindowCursor, LocalObjectToPack, R, IOException> func) {
+ if (objectId instanceof LocalObjectToPack) {
+ LocalObjectToPack lotp = (LocalObjectToPack) objectId;
+ Pack pack = lotp.pack;
+ if (pack != null) {
+ try {
+ return func.apply(pack, curs, lotp);
+ } catch (IOException e) {
+ // lotp potentially repacked, continue as if lotp not
+ // provided
+ }
+ }
+ }
+ return null;
+ }
+
@Override
void selectObjectRepresentation(PackWriter packer, ObjectToPack otp,
WindowCursor curs) throws IOException {
@@ -559,11 +581,11 @@ public class ObjectDirectory extends FileObjectDatabase {
// If the object is already in the repository, remove temporary file.
//
if (loose.hasCached(id)) {
- FileUtils.delete(tmp, FileUtils.RETRY);
+ FileUtils.delete(tmp, FileUtils.RETRY | FileUtils.SKIP_MISSING);
return InsertLooseObjectResult.EXISTS_LOOSE;
}
if (!createDuplicate && has(id)) {
- FileUtils.delete(tmp, FileUtils.RETRY);
+ FileUtils.delete(tmp, FileUtils.RETRY | FileUtils.SKIP_MISSING);
return InsertLooseObjectResult.EXISTS_PACKED;
}
return loose.insert(tmp, id);
@@ -811,7 +833,6 @@ public class ObjectDirectory extends FileObjectDatabase {
}
}
- /** {@inheritDoc} */
@Override
public ObjectDatabase newCachedDatabase() {
return newCachedFileObjectDatabase();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java
index e6b2cc12f4..24fead86bd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java
@@ -49,7 +49,6 @@ class ObjectDirectoryInserter extends ObjectInserter {
config = cfg.get(WriteConfig.KEY);
}
- /** {@inheritDoc} */
@Override
public ObjectId insert(int type, byte[] data, int off, int len)
throws IOException {
@@ -62,12 +61,19 @@ class ObjectDirectoryInserter extends ObjectInserter {
* ODB.
*
* @param type
+ * object type
* @param data
+ * object data
* @param off
+ * first position within @{code data}
* @param len
+ * length number of bytes to copy
* @param createDuplicate
- * @return ObjectId
+ * whether to insert a duplicate if an object with this id
+ * already exists
+ * @return ObjectId the name of the object
* @throws IOException
+ * if an IO error occurred
*/
private ObjectId insert(
int type, byte[] data, int off, int len, boolean createDuplicate)
@@ -80,7 +86,6 @@ class ObjectDirectoryInserter extends ObjectInserter {
return insertOneObject(tmp, id, createDuplicate);
}
- /** {@inheritDoc} */
@Override
public ObjectId insert(int type, long len, InputStream is)
throws IOException {
@@ -93,11 +98,17 @@ class ObjectDirectoryInserter extends ObjectInserter {
* ODB.
*
* @param type
+ * object type
* @param len
+ * number of bytes to copy
* @param is
+ * input stream
* @param createDuplicate
- * @return ObjectId
+ * whether to insert a duplicate if an object with this id
+ * already exists
+ * @return ObjectId the name of the object
* @throws IOException
+ * if an IO error occurred
*/
ObjectId insert(int type, long len, InputStream is, boolean createDuplicate)
throws IOException {
@@ -132,25 +143,21 @@ class ObjectDirectoryInserter extends ObjectInserter {
.format(JGitText.get().unableToCreateNewObject, dst));
}
- /** {@inheritDoc} */
@Override
public PackParser newPackParser(InputStream in) throws IOException {
return new ObjectDirectoryPackParser(db, in);
}
- /** {@inheritDoc} */
@Override
public ObjectReader newReader() {
return new WindowCursor(db, this);
}
- /** {@inheritDoc} */
@Override
public void flush() throws IOException {
// Do nothing. Loose objects are immediately visible.
}
- /** {@inheritDoc} */
@Override
public void close() {
if (deflate != null) {
@@ -197,7 +204,7 @@ class ObjectDirectoryInserter extends ObjectInserter {
return tmp;
} finally {
if (delete) {
- FileUtils.delete(tmp, FileUtils.RETRY);
+ FileUtils.delete(tmp, FileUtils.RETRY | FileUtils.SKIP_MISSING);
}
}
}
@@ -225,7 +232,7 @@ class ObjectDirectoryInserter extends ObjectInserter {
return tmp;
} finally {
if (delete) {
- FileUtils.delete(tmp, FileUtils.RETRY);
+ FileUtils.delete(tmp, FileUtils.RETRY | FileUtils.SKIP_MISSING);
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
index 55b2646c46..d97d5a7ccd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
@@ -28,6 +28,7 @@ import java.util.zip.Deflater;
import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.internal.storage.pack.PackIndexWriter;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig;
@@ -71,6 +72,12 @@ public class ObjectDirectoryPackParser extends PackParser {
*/
private File tmpIdx;
+ /**
+ * Path of the object-size index created for the pack, to filter quickly
+ * objects by size in partial clones
+ */
+ private File tmpObjectSizeIndex;
+
/** Read/write handle to {@link #tmpPack} while it is being parsed. */
private RandomAccessFile out;
@@ -110,7 +117,7 @@ public class ObjectDirectoryPackParser extends PackParser {
* @param version
* the version to write. The special version 0 designates the
* oldest (most compatible) format available for the objects.
- * @see PackIndexWriter
+ * @see BasePackIndexWriter
*/
public void setIndexVersion(int version) {
indexVersion = version;
@@ -142,7 +149,6 @@ public class ObjectDirectoryPackParser extends PackParser {
return newPack;
}
- /** {@inheritDoc} */
@Override
public long getPackSize() {
if (newPack == null)
@@ -158,12 +164,12 @@ public class ObjectDirectoryPackParser extends PackParser {
return size;
}
- /** {@inheritDoc} */
@Override
public PackLock parse(ProgressMonitor receiving, ProgressMonitor resolving)
throws IOException {
tmpPack = File.createTempFile("incoming_", ".pack", db.getDirectory()); //$NON-NLS-1$ //$NON-NLS-2$
tmpIdx = new File(db.getDirectory(), baseName(tmpPack) + ".idx"); //$NON-NLS-1$
+
try {
out = new RandomAccessFile(tmpPack, "rw"); //$NON-NLS-1$
@@ -179,6 +185,14 @@ public class ObjectDirectoryPackParser extends PackParser {
tmpPack.setReadOnly();
tmpIdx.setReadOnly();
+ if (pconfig.isWriteObjSizeIndex()) {
+ tmpObjectSizeIndex = new File(db.getDirectory(),
+ baseName(tmpPack)
+ + PackExt.OBJECT_SIZE_INDEX.getExtension());
+ writeObjectSizeIndex(pconfig.getMinBytesForObjSizeIndex());
+ tmpObjectSizeIndex.setReadOnly();
+ }
+
return renameAndOpenPack(getLockMessage());
} finally {
if (def != null)
@@ -193,40 +207,34 @@ public class ObjectDirectoryPackParser extends PackParser {
}
}
- /** {@inheritDoc} */
@Override
protected void onPackHeader(long objectCount) throws IOException {
// Ignored, the count is not required.
}
- /** {@inheritDoc} */
@Override
protected void onBeginWholeObject(long streamPosition, int type,
long inflatedSize) throws IOException {
crc.reset();
}
- /** {@inheritDoc} */
@Override
protected void onEndWholeObject(PackedObjectInfo info) throws IOException {
info.setCRC((int) crc.getValue());
}
- /** {@inheritDoc} */
@Override
protected void onBeginOfsDelta(long streamPosition,
long baseStreamPosition, long inflatedSize) throws IOException {
crc.reset();
}
- /** {@inheritDoc} */
@Override
protected void onBeginRefDelta(long streamPosition, AnyObjectId baseId,
long inflatedSize) throws IOException {
crc.reset();
}
- /** {@inheritDoc} */
@Override
protected UnresolvedDelta onEndDelta() throws IOException {
UnresolvedDelta delta = new UnresolvedDelta();
@@ -234,35 +242,30 @@ public class ObjectDirectoryPackParser extends PackParser {
return delta;
}
- /** {@inheritDoc} */
@Override
protected void onInflatedObjectData(PackedObjectInfo obj, int typeCode,
byte[] data) throws IOException {
// ObjectDirectory ignores this event.
}
- /** {@inheritDoc} */
@Override
protected void onObjectHeader(Source src, byte[] raw, int pos, int len)
throws IOException {
crc.update(raw, pos, len);
}
- /** {@inheritDoc} */
@Override
protected void onObjectData(Source src, byte[] raw, int pos, int len)
throws IOException {
crc.update(raw, pos, len);
}
- /** {@inheritDoc} */
@Override
protected void onStoreStream(byte[] raw, int pos, int len)
throws IOException {
out.write(raw, pos, len);
}
- /** {@inheritDoc} */
@Override
protected void onPackFooter(byte[] hash) throws IOException {
packEnd = out.getFilePointer();
@@ -271,7 +274,6 @@ public class ObjectDirectoryPackParser extends PackParser {
packHash = hash;
}
- /** {@inheritDoc} */
@Override
protected ObjectTypeAndSize seekDatabase(UnresolvedDelta delta,
ObjectTypeAndSize info) throws IOException {
@@ -280,7 +282,6 @@ public class ObjectDirectoryPackParser extends PackParser {
return readObjectHeader(info);
}
- /** {@inheritDoc} */
@Override
protected ObjectTypeAndSize seekDatabase(PackedObjectInfo obj,
ObjectTypeAndSize info) throws IOException {
@@ -289,13 +290,11 @@ public class ObjectDirectoryPackParser extends PackParser {
return readObjectHeader(info);
}
- /** {@inheritDoc} */
@Override
protected int readDatabase(byte[] dst, int pos, int cnt) throws IOException {
return out.read(dst, pos, cnt);
}
- /** {@inheritDoc} */
@Override
protected boolean checkCRC(int oldCRC) {
return oldCRC == (int) crc.getValue();
@@ -311,9 +310,11 @@ public class ObjectDirectoryPackParser extends PackParser {
tmpIdx.deleteOnExit();
if (tmpPack != null && !tmpPack.delete() && tmpPack.exists())
tmpPack.deleteOnExit();
+ if (tmpObjectSizeIndex != null && !tmpObjectSizeIndex.delete()
+ && tmpObjectSizeIndex.exists())
+ tmpPack.deleteOnExit();
}
- /** {@inheritDoc} */
@Override
protected boolean onAppendBase(final int typeCode, final byte[] data,
final PackedObjectInfo info) throws IOException {
@@ -356,7 +357,6 @@ public class ObjectDirectoryPackParser extends PackParser {
return true;
}
- /** {@inheritDoc} */
@Override
protected void onEndThinPack() throws IOException {
final byte[] buf = buffer();
@@ -405,14 +405,23 @@ public class ObjectDirectoryPackParser extends PackParser {
try (FileOutputStream os = new FileOutputStream(tmpIdx)) {
final PackIndexWriter iw;
if (indexVersion <= 0)
- iw = PackIndexWriter.createOldestPossible(os, list);
+ iw = BasePackIndexWriter.createOldestPossible(os, list);
else
- iw = PackIndexWriter.createVersion(os, indexVersion);
+ iw = BasePackIndexWriter.createVersion(os, indexVersion);
iw.write(list, packHash);
os.getChannel().force(true);
}
}
+ private void writeObjectSizeIndex(int minSize) throws IOException {
+ try (FileOutputStream os = new FileOutputStream(tmpObjectSizeIndex)) {
+ PackObjectSizeIndexWriter iw = PackObjectSizeIndexWriter
+ .createWriter(os, minSize);
+ iw.write(getSortedObjectList(null));
+ os.getChannel().force(true);
+ }
+ }
+
private PackLock renameAndOpenPack(String lockMessage)
throws IOException {
if (!keepEmpty && getObjectCount() == 0) {
@@ -487,6 +496,27 @@ public class ObjectDirectoryPackParser extends PackParser {
JGitText.get().cannotMoveIndexTo, finalIdx), e);
}
+ if (pconfig.isWriteObjSizeIndex() && tmpObjectSizeIndex != null) {
+ PackFile finalObjectSizeIndex = finalPack
+ .create(PackExt.OBJECT_SIZE_INDEX);
+ try {
+ FileUtils.rename(tmpObjectSizeIndex, finalObjectSizeIndex,
+ StandardCopyOption.ATOMIC_MOVE);
+ } catch (IOException e) {
+ cleanupTemporaryFiles();
+ keep.unlock();
+ if (!finalPack.delete())
+ finalPack.deleteOnExit();
+ if (!finalIdx.delete()) {
+ finalIdx.deleteOnExit();
+ }
+ throw new IOException(MessageFormat
+ .format(JGitText.get().cannotMoveIndexTo,
+ finalObjectSizeIndex),
+ e);
+ }
+ }
+
boolean interrupted = false;
try {
FileSnapshot snapshot = FileSnapshot.save(finalPack);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java
index 6e74136c1b..8988b41b55 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/Pack.java
@@ -14,9 +14,14 @@ package org.eclipse.jgit.internal.storage.file;
import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.KEEP;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.REVERSE_INDEX;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PACKED_INDEX_GIT_USE_STRONGREFS;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.OBJECT_SIZE_INDEX;
import java.io.EOFException;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InterruptedIOException;
@@ -31,6 +36,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.CRC32;
@@ -50,12 +56,17 @@ import org.eclipse.jgit.errors.UnsupportedPackIndexVersionException;
import org.eclipse.jgit.errors.UnsupportedPackVersionException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.pack.BinaryDelta;
+import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
+import org.eclipse.jgit.internal.util.Optionally;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.Hex;
import org.eclipse.jgit.util.LongList;
import org.eclipse.jgit.util.NB;
import org.eclipse.jgit.util.RawParseUtils;
@@ -76,6 +87,8 @@ public class Pack implements Iterable<PackIndex.MutableEntry> {
public static final Comparator<Pack> SORT = (a, b) -> b.packLastModified
.compareTo(a.packLastModified);
+ private boolean useStrongRefs;
+
private final PackFile packFile;
private PackFile keepFile;
@@ -84,6 +97,9 @@ public class Pack implements Iterable<PackIndex.MutableEntry> {
private RandomAccessFile fd;
+ /** For managing open/close accounting of {@link #fd}. */
+ private final Object activeLock = new Object();
+
/** Serializes reads performed against {@link #fd}. */
private final Object readLock = new Object();
@@ -102,17 +118,22 @@ public class Pack implements Iterable<PackIndex.MutableEntry> {
private volatile Exception invalidatingCause;
@Nullable
- private PackFile bitmapIdxFile;
+ private volatile PackFile bitmapIdxFile;
private AtomicInteger transientErrorCount = new AtomicInteger();
private byte[] packChecksum;
- private volatile PackIndex loadedIdx;
+ private volatile Optionally<PackIndex> loadedIdx = Optionally.empty();
+
+ private volatile Optionally<PackReverseIndex> reverseIdx = Optionally.empty();
+
+ private volatile PackObjectSizeIndex loadedObjSizeIdx;
- private PackReverseIndex reverseIdx;
+ private volatile boolean attemptLoadObjSizeIdx;
+
+ private volatile Optionally<PackBitmapIndex> bitmapIdx = Optionally.empty();
- private PackBitmapIndex bitmapIdx;
/**
* Objects we have tried to read, and discovered to be corrupt.
@@ -126,12 +147,16 @@ public class Pack implements Iterable<PackIndex.MutableEntry> {
/**
* Construct a reader for an existing, pre-indexed packfile.
*
+ * @param cfg
+ * configuration this directory consults for write settings.
* @param packFile
* path of the <code>.pack</code> file holding the data.
* @param bitmapIdxFile
* existing bitmap index file with the same base as the pack
*/
- public Pack(File packFile, @Nullable PackFile bitmapIdxFile) {
+ public Pack(Config cfg, File packFile, @Nullable PackFile bitmapIdxFile) {
+ useStrongRefs = cfg.getBoolean(CONFIG_CORE_SECTION,
+ CONFIG_KEY_PACKED_INDEX_GIT_USE_STRONGREFS, WindowCache.getInstance().isPackedIndexGitUseStrongRefs());
this.packFile = new PackFile(packFile);
this.fileSnapshot = PackFileSnapshot.save(packFile);
this.packLastModified = fileSnapshot.lastModifiedInstant();
@@ -145,58 +170,107 @@ public class Pack implements Iterable<PackIndex.MutableEntry> {
}
private PackIndex idx() throws IOException {
- PackIndex idx = loadedIdx;
- if (idx == null) {
- synchronized (this) {
- idx = loadedIdx;
- if (idx == null) {
- if (invalid) {
- throw new PackInvalidException(packFile,
- invalidatingCause);
- }
- try {
- long start = System.currentTimeMillis();
- PackFile idxFile = packFile.create(INDEX);
- idx = PackIndex.open(idxFile);
- if (LOG.isDebugEnabled()) {
- LOG.debug(String.format(
- "Opening pack index %s, size %.3f MB took %d ms", //$NON-NLS-1$
- idxFile.getAbsolutePath(),
- Float.valueOf(idxFile.length()
- / (1024f * 1024)),
- Long.valueOf(System.currentTimeMillis()
- - start)));
- }
+ Optional<PackIndex> optional = loadedIdx.getOptional();
+ if (optional.isPresent()) {
+ return optional.get();
+ }
+ return memoizeIdxIfNeeded();
+ }
- if (packChecksum == null) {
- packChecksum = idx.packChecksum;
- fileSnapshot.setChecksum(
- ObjectId.fromRaw(packChecksum));
- } else if (!Arrays.equals(packChecksum,
- idx.packChecksum)) {
- throw new PackMismatchException(MessageFormat
- .format(JGitText.get().packChecksumMismatch,
- packFile.getPath(),
- ObjectId.fromRaw(packChecksum)
- .name(),
- ObjectId.fromRaw(idx.packChecksum)
- .name()));
- }
- loadedIdx = idx;
- } catch (InterruptedIOException e) {
- // don't invalidate the pack, we are interrupted from
- // another thread
- throw e;
- } catch (IOException e) {
- invalid = true;
- invalidatingCause = e;
- throw e;
- }
+ private synchronized PackIndex memoizeIdxIfNeeded() throws IOException {
+ Optional<PackIndex> optional = loadedIdx.getOptional();
+ if (optional.isPresent()) {
+ return optional.get();
+ }
+ if (invalid) {
+ throw new PackInvalidException(packFile, invalidatingCause);
+ }
+ try {
+ long start = System.currentTimeMillis();
+ PackFile idxFile = packFile.create(INDEX);
+ PackIndex idx = PackIndex.open(idxFile);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(String.format(
+ "Opening pack index %s, size %.3f MB took %d ms", //$NON-NLS-1$
+ idxFile.getAbsolutePath(),
+ Float.valueOf(idxFile.length()
+ / (1024f * 1024)),
+ Long.valueOf(System.currentTimeMillis()
+ - start)));
+ }
+ if (packChecksum == null) {
+ packChecksum = idx.getChecksum();
+ fileSnapshot.setChecksum(
+ ObjectId.fromRaw(packChecksum));
+ } else if (!Arrays.equals(packChecksum,
+ idx.getChecksum())) {
+ throw new PackMismatchException(MessageFormat
+ .format(JGitText.get().packChecksumMismatch,
+ packFile.getPath(),
+ PackExt.PACK.getExtension(),
+ Hex.toHexString(packChecksum),
+ PackExt.INDEX.getExtension(),
+ Hex.toHexString(idx.getChecksum())));
+ }
+ loadedIdx = optionally(idx);
+ return idx;
+ } catch (InterruptedIOException e) {
+ // don't invalidate the pack, we are interrupted from
+ // another thread
+ throw e;
+ } catch (IOException e) {
+ invalid = true;
+ invalidatingCause = e;
+ throw e;
+ }
+ }
+
+ private PackObjectSizeIndex objectSizeIndex() throws IOException {
+ if (loadedObjSizeIdx != null) {
+ return loadedObjSizeIdx;
+ }
+
+ if (attemptLoadObjSizeIdx) {
+ return null;
+ }
+
+ synchronized (this) {
+ if (loadedObjSizeIdx != null) {
+ return loadedObjSizeIdx;
+ }
+
+ PackObjectSizeIndex sizeIdx;
+ try {
+ long start = System.currentTimeMillis();
+ PackFile sizeIdxFile = packFile.create(OBJECT_SIZE_INDEX);
+ if (attemptLoadObjSizeIdx || !sizeIdxFile.exists()) {
+ attemptLoadObjSizeIdx = true;
+ return null;
}
+ sizeIdx = PackObjectSizeIndexLoader.load(
+ new FileInputStream(sizeIdxFile.getAbsoluteFile()));
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(String.format(
+ "Opening obj size index %s, size %.3f MB took %d ms", //$NON-NLS-1$
+ sizeIdxFile.getAbsolutePath(),
+ Float.valueOf(
+ sizeIdxFile.length() / (1024f * 1024)),
+ Long.valueOf(System.currentTimeMillis() - start)));
+ }
+
+ loadedObjSizeIdx = sizeIdx;
+ } catch (InterruptedIOException e) {
+ // don't invalidate the pack, we are interrupted from
+ // another thread
+ return null;
+ } finally {
+ attemptLoadObjSizeIdx = true;
}
}
- return idx;
+
+ return loadedObjSizeIdx;
}
+
/**
* Get the File object which locates this pack on disk.
*
@@ -211,12 +285,69 @@ public class Pack implements Iterable<PackIndex.MutableEntry> {
*
* @return the index for this pack file.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public PackIndex getIndex() throws IOException {
return idx();
}
/**
+ * Get the object size index for this pack file
+ *
+ * @return the object size index for this pack file if it exists (null
+ * otherwise)
+ * @throws IOException
+ * problem reading the index
+ */
+ public boolean hasObjectSizeIndex() throws IOException {
+ return objectSizeIndex() != null;
+ }
+
+ /**
+ * Number of objects in the object-size index of this pack
+ *
+ * @return number of objects in the index (0 if either the index is empty or
+ * it doesn't exist)
+ * @throws IOException
+ * if an IO error occurred while reading the index
+ */
+ public long getObjectSizeIndexCount() throws IOException {
+ if (!hasObjectSizeIndex()) {
+ return 0;
+ }
+
+ return objectSizeIndex().getObjectCount();
+ }
+
+ /**
+ * Return the size of the object from the object-size index.
+ *
+ * Caller MUST check that the pack has object-size index
+ * ({@link #hasObjectSizeIndex()}) and that the pack contains the object.
+ *
+ * @param id
+ * object id of an object in the pack
+ * @return size of the object from the index. Negative if the object is not
+ * in the index.
+ * @throws IOException
+ * if an IO error occurred while reading the index
+ */
+ public long getIndexedObjectSize(AnyObjectId id) throws IOException {
+ int idxPos = idx().findPosition(id);
+ if (idxPos < 0) {
+ return -1;
+ }
+
+ PackObjectSizeIndex sizeIdx = objectSizeIndex();
+ if (sizeIdx == null) {
+ throw new IllegalStateException(
+ "Asking indexed size from a pack without object size index"); //$NON-NLS-1$
+ }
+
+ return sizeIdx.getSize(idxPos);
+ }
+
+ /**
* Get name extracted from {@code pack-*.pack} pattern.
*
* @return name extracted from {@code pack-*.pack} pattern.
@@ -279,14 +410,28 @@ public class Pack implements Iterable<PackIndex.MutableEntry> {
}
/**
- * Close the resources utilized by this repository
+ * Close the resources utilized by these pack files
+ *
+ * @param packs
+ * packs to close
+ */
+ public static void close(Set<Pack> packs) {
+ WindowCache.purge(packs);
+ packs.forEach(p -> p.closeIndices());
+ }
+
+ /**
+ * Close the resources utilized by this pack file
*/
public void close() {
WindowCache.purge(this);
- synchronized (this) {
- loadedIdx = null;
- reverseIdx = null;
- }
+ closeIndices();
+ }
+
+ private synchronized void closeIndices() {
+ loadedIdx = Optionally.empty();
+ reverseIdx = Optionally.empty();
+ bitmapIdx = Optionally.empty();
}
/**
@@ -398,185 +543,202 @@ public class Pack implements Iterable<PackIndex.MutableEntry> {
final CRC32 crc2 = validate ? new CRC32() : null;
final byte[] buf = out.getCopyBuffer();
+ boolean isHeaderWritten = false;
// Rip apart the header so we can discover the size.
//
- readFully(src.offset, buf, 0, 20, curs);
- int c = buf[0] & 0xff;
- final int typeCode = (c >> 4) & 7;
- long inflatedLength = c & 15;
- int shift = 4;
- int headerCnt = 1;
- while ((c & 0x80) != 0) {
- c = buf[headerCnt++] & 0xff;
- inflatedLength += ((long) (c & 0x7f)) << shift;
- shift += 7;
- }
-
- if (typeCode == Constants.OBJ_OFS_DELTA) {
- do {
+ try {
+ readFully(src.offset, buf, 0, 20, curs);
+
+ int c = buf[0] & 0xff;
+ final int typeCode = (c >> 4) & 7;
+ long inflatedLength = c & 15;
+ int shift = 4;
+ int headerCnt = 1;
+ while ((c & 0x80) != 0) {
c = buf[headerCnt++] & 0xff;
- } while ((c & 128) != 0);
- if (validate) {
- assert(crc1 != null && crc2 != null);
- crc1.update(buf, 0, headerCnt);
- crc2.update(buf, 0, headerCnt);
+ inflatedLength += ((long) (c & 0x7f)) << shift;
+ shift += 7;
}
- } else if (typeCode == Constants.OBJ_REF_DELTA) {
- if (validate) {
+
+ if (typeCode == Constants.OBJ_OFS_DELTA) {
+ do {
+ c = buf[headerCnt++] & 0xff;
+ } while ((c & 128) != 0);
+ if (validate) {
+ assert(crc1 != null && crc2 != null);
+ crc1.update(buf, 0, headerCnt);
+ crc2.update(buf, 0, headerCnt);
+ }
+ } else if (typeCode == Constants.OBJ_REF_DELTA) {
+ if (validate) {
+ assert(crc1 != null && crc2 != null);
+ crc1.update(buf, 0, headerCnt);
+ crc2.update(buf, 0, headerCnt);
+ }
+
+ readFully(src.offset + headerCnt, buf, 0, 20, curs);
+ if (validate) {
+ assert(crc1 != null && crc2 != null);
+ crc1.update(buf, 0, 20);
+ crc2.update(buf, 0, 20);
+ }
+ headerCnt += 20;
+ } else if (validate) {
assert(crc1 != null && crc2 != null);
crc1.update(buf, 0, headerCnt);
crc2.update(buf, 0, headerCnt);
}
- readFully(src.offset + headerCnt, buf, 0, 20, curs);
- if (validate) {
- assert(crc1 != null && crc2 != null);
- crc1.update(buf, 0, 20);
- crc2.update(buf, 0, 20);
- }
- headerCnt += 20;
- } else if (validate) {
- assert(crc1 != null && crc2 != null);
- crc1.update(buf, 0, headerCnt);
- crc2.update(buf, 0, headerCnt);
- }
+ final long dataOffset = src.offset + headerCnt;
+ final long dataLength = src.length;
+ final long expectedCRC;
+ final ByteArrayWindow quickCopy;
- final long dataOffset = src.offset + headerCnt;
- final long dataLength = src.length;
- final long expectedCRC;
- final ByteArrayWindow quickCopy;
-
- // Verify the object isn't corrupt before sending. If it is,
- // we report it missing instead.
- //
- try {
- quickCopy = curs.quickCopy(this, dataOffset, dataLength);
+ // Verify the object isn't corrupt before sending. If it is,
+ // we report it missing instead.
+ //
+ try {
+ quickCopy = curs.quickCopy(this, dataOffset, dataLength);
- if (validate && idx().hasCRC32Support()) {
- assert(crc1 != null);
- // Index has the CRC32 code cached, validate the object.
- //
- expectedCRC = idx().findCRC32(src);
- if (quickCopy != null) {
- quickCopy.crc32(crc1, dataOffset, (int) dataLength);
- } else {
- long pos = dataOffset;
- long cnt = dataLength;
- while (cnt > 0) {
- final int n = (int) Math.min(cnt, buf.length);
- readFully(pos, buf, 0, n, curs);
- crc1.update(buf, 0, n);
- pos += n;
- cnt -= n;
+ if (validate && idx().hasCRC32Support()) {
+ assert(crc1 != null);
+ // Index has the CRC32 code cached, validate the object.
+ //
+ expectedCRC = idx().findCRC32(src);
+ if (quickCopy != null) {
+ quickCopy.crc32(crc1, dataOffset, (int) dataLength);
+ } else {
+ long pos = dataOffset;
+ long cnt = dataLength;
+ while (cnt > 0) {
+ final int n = (int) Math.min(cnt, buf.length);
+ readFully(pos, buf, 0, n, curs);
+ crc1.update(buf, 0, n);
+ pos += n;
+ cnt -= n;
+ }
}
+ if (crc1.getValue() != expectedCRC) {
+ setCorrupt(src.offset);
+ throw new CorruptObjectException(MessageFormat.format(
+ JGitText.get().objectAtHasBadZlibStream,
+ Long.valueOf(src.offset), getPackFile()));
+ }
+ } else if (validate) {
+ // We don't have a CRC32 code in the index, so compute it
+ // now while inflating the raw data to get zlib to tell us
+ // whether or not the data is safe.
+ //
+ Inflater inf = curs.inflater();
+ byte[] tmp = new byte[1024];
+ if (quickCopy != null) {
+ quickCopy.check(inf, tmp, dataOffset, (int) dataLength);
+ } else {
+ assert(crc1 != null);
+ long pos = dataOffset;
+ long cnt = dataLength;
+ while (cnt > 0) {
+ final int n = (int) Math.min(cnt, buf.length);
+ readFully(pos, buf, 0, n, curs);
+ crc1.update(buf, 0, n);
+ inf.setInput(buf, 0, n);
+ while (inf.inflate(tmp, 0, tmp.length) > 0)
+ continue;
+ pos += n;
+ cnt -= n;
+ }
+ }
+ if (!inf.finished() || inf.getBytesRead() != dataLength) {
+ setCorrupt(src.offset);
+ throw new EOFException(MessageFormat.format(
+ JGitText.get().shortCompressedStreamAt,
+ Long.valueOf(src.offset)));
+ }
+ assert(crc1 != null);
+ expectedCRC = crc1.getValue();
+ } else {
+ expectedCRC = -1;
}
- if (crc1.getValue() != expectedCRC) {
- setCorrupt(src.offset);
- throw new CorruptObjectException(MessageFormat.format(
- JGitText.get().objectAtHasBadZlibStream,
- Long.valueOf(src.offset), getPackFile()));
- }
- } else if (validate) {
- // We don't have a CRC32 code in the index, so compute it
- // now while inflating the raw data to get zlib to tell us
- // whether or not the data is safe.
+ } catch (DataFormatException dataFormat) {
+ setCorrupt(src.offset);
+
+ CorruptObjectException corruptObject = new CorruptObjectException(
+ MessageFormat.format(
+ JGitText.get().objectAtHasBadZlibStream,
+ Long.valueOf(src.offset), getPackFile()),
+ dataFormat);
+
+ throw new StoredObjectRepresentationNotAvailableException(
+ corruptObject);
+ }
+
+ if (quickCopy != null) {
+ // The entire object fits into a single byte array window slice,
+ // and we have it pinned. Write this out without copying.
//
- Inflater inf = curs.inflater();
- byte[] tmp = new byte[1024];
- if (quickCopy != null) {
- quickCopy.check(inf, tmp, dataOffset, (int) dataLength);
- } else {
- assert(crc1 != null);
+ out.writeHeader(src, inflatedLength);
+ isHeaderWritten = true;
+ quickCopy.write(out, dataOffset, (int) dataLength);
+
+ } else if (dataLength <= buf.length) {
+ // Tiny optimization: Lots of objects are very small deltas or
+ // deflated commits that are likely to fit in the copy buffer.
+ //
+ if (!validate) {
long pos = dataOffset;
long cnt = dataLength;
while (cnt > 0) {
final int n = (int) Math.min(cnt, buf.length);
readFully(pos, buf, 0, n, curs);
- crc1.update(buf, 0, n);
- inf.setInput(buf, 0, n);
- while (inf.inflate(tmp, 0, tmp.length) > 0)
- continue;
pos += n;
cnt -= n;
}
}
- if (!inf.finished() || inf.getBytesRead() != dataLength) {
- setCorrupt(src.offset);
- throw new EOFException(MessageFormat.format(
- JGitText.get().shortCompressedStreamAt,
- Long.valueOf(src.offset)));
- }
- assert(crc1 != null);
- expectedCRC = crc1.getValue();
+ out.writeHeader(src, inflatedLength);
+ isHeaderWritten = true;
+ out.write(buf, 0, (int) dataLength);
} else {
- expectedCRC = -1;
- }
- } catch (DataFormatException dataFormat) {
- setCorrupt(src.offset);
-
- CorruptObjectException corruptObject = new CorruptObjectException(
- MessageFormat.format(
- JGitText.get().objectAtHasBadZlibStream,
- Long.valueOf(src.offset), getPackFile()),
- dataFormat);
-
- throw new StoredObjectRepresentationNotAvailableException(
- corruptObject);
-
- } catch (IOException ioError) {
- throw new StoredObjectRepresentationNotAvailableException(ioError);
- }
-
- if (quickCopy != null) {
- // The entire object fits into a single byte array window slice,
- // and we have it pinned. Write this out without copying.
- //
- out.writeHeader(src, inflatedLength);
- quickCopy.write(out, dataOffset, (int) dataLength);
-
- } else if (dataLength <= buf.length) {
- // Tiny optimization: Lots of objects are very small deltas or
- // deflated commits that are likely to fit in the copy buffer.
- //
- if (!validate) {
+ // Now we are committed to sending the object. As we spool it out,
+ // check its CRC32 code to make sure there wasn't corruption between
+ // the verification we did above, and us actually outputting it.
+ //
long pos = dataOffset;
long cnt = dataLength;
while (cnt > 0) {
final int n = (int) Math.min(cnt, buf.length);
readFully(pos, buf, 0, n, curs);
- pos += n;
+ if (validate) {
+ assert(crc2 != null);
+ crc2.update(buf, 0, n);
+ }
cnt -= n;
+ if (!isHeaderWritten) {
+ if (invalid && cnt > 0) {
+ // Since this is not the last iteration and the packfile is invalid,
+ // better to assume the iterations will not all complete here while
+ // it is still likely recoverable.
+ throw new StoredObjectRepresentationNotAvailableException(invalidatingCause);
+ }
+ out.writeHeader(src, inflatedLength);
+ isHeaderWritten = true;
+ }
+ out.write(buf, 0, n);
+ pos += n;
}
- }
- out.writeHeader(src, inflatedLength);
- out.write(buf, 0, (int) dataLength);
- } else {
- // Now we are committed to sending the object. As we spool it out,
- // check its CRC32 code to make sure there wasn't corruption between
- // the verification we did above, and us actually outputting it.
- //
- out.writeHeader(src, inflatedLength);
- long pos = dataOffset;
- long cnt = dataLength;
- while (cnt > 0) {
- final int n = (int) Math.min(cnt, buf.length);
- readFully(pos, buf, 0, n, curs);
if (validate) {
assert(crc2 != null);
- crc2.update(buf, 0, n);
+ if (crc2.getValue() != expectedCRC) {
+ throw new CorruptObjectException(MessageFormat.format(
+ JGitText.get().objectAtHasBadZlibStream,
+ Long.valueOf(src.offset), getPackFile()));
+ }
}
- out.write(buf, 0, n);
- pos += n;
- cnt -= n;
}
- if (validate) {
- assert(crc2 != null);
- if (crc2.getValue() != expectedCRC) {
- throw new CorruptObjectException(MessageFormat.format(
- JGitText.get().objectAtHasBadZlibStream,
- Long.valueOf(src.offset), getPackFile()));
- }
+ } catch (IOException ioError) {
+ if (!isHeaderWritten) {
+ throw new StoredObjectRepresentationNotAvailableException(ioError);
}
+ throw ioError;
}
}
@@ -603,42 +765,53 @@ public class Pack implements Iterable<PackIndex.MutableEntry> {
throw new EOFException();
}
- private synchronized void beginCopyAsIs()
+ private void beginCopyAsIs()
throws StoredObjectRepresentationNotAvailableException {
- if (++activeCopyRawData == 1 && activeWindows == 0) {
- try {
- doOpen();
- } catch (IOException thisPackNotValid) {
- throw new StoredObjectRepresentationNotAvailableException(
- thisPackNotValid);
+ synchronized (activeLock) {
+ if (++activeCopyRawData == 1 && activeWindows == 0) {
+ try {
+ doOpen();
+ } catch (IOException thisPackNotValid) {
+ throw new StoredObjectRepresentationNotAvailableException(
+ thisPackNotValid);
+ }
}
}
}
- private synchronized void endCopyAsIs() {
- if (--activeCopyRawData == 0 && activeWindows == 0)
- doClose();
+ private void endCopyAsIs() {
+ synchronized (activeLock) {
+ if (--activeCopyRawData == 0 && activeWindows == 0) {
+ doClose();
+ }
+ }
}
- synchronized boolean beginWindowCache() throws IOException {
- if (++activeWindows == 1) {
- if (activeCopyRawData == 0)
- doOpen();
- return true;
+ boolean beginWindowCache() throws IOException {
+ synchronized (activeLock) {
+ if (++activeWindows == 1) {
+ if (activeCopyRawData == 0) {
+ doOpen();
+ }
+ return true;
+ }
+ return false;
}
- return false;
}
- synchronized boolean endWindowCache() {
- final boolean r = --activeWindows == 0;
- if (r && activeCopyRawData == 0)
- doClose();
- return r;
+ boolean endWindowCache() {
+ synchronized (activeLock) {
+ boolean r = --activeWindows == 0;
+ if (r && activeCopyRawData == 0) {
+ doClose();
+ }
+ return r;
+ }
}
private void doOpen() throws IOException {
if (invalid) {
- openFail(true, invalidatingCause);
+ openFail(invalidatingCause);
throw new PackInvalidException(packFile, invalidatingCause);
}
try {
@@ -649,34 +822,41 @@ public class Pack implements Iterable<PackIndex.MutableEntry> {
}
} catch (InterruptedIOException e) {
// don't invalidate the pack, we are interrupted from another thread
- openFail(false, e);
+ openFail(e);
throw e;
} catch (FileNotFoundException fn) {
- // don't invalidate the pack if opening an existing file failed
- // since it may be related to a temporary lack of resources (e.g.
- // max open files)
- openFail(!packFile.exists(), fn);
+ if (!packFile.exists()) {
+ // Failure to open an existing file may be related to a temporary lack of resources
+ // (e.g. max open files)
+ invalid = true;
+ }
+ openFail(fn);
throw fn;
} catch (EOFException | AccessDeniedException | NoSuchFileException
| CorruptObjectException | NoPackSignatureException
| PackMismatchException | UnpackException
| UnsupportedPackIndexVersionException
| UnsupportedPackVersionException pe) {
- // exceptions signaling permanent problems with a pack
- openFail(true, pe);
+ invalid = true; // exceptions signaling permanent problems with a pack
+ openFail(pe);
throw pe;
- } catch (IOException | RuntimeException ge) {
+ } catch (IOException ioe) {
+ if (FileUtils.isStaleFileHandleInCausalChain(ioe)) {
+ invalid = true;
+ }
+ openFail(ioe);
+ throw ioe;
+ } catch (RuntimeException ge) {
// generic exceptions could be transient so we should not mark the
// pack invalid to avoid false MissingObjectExceptions
- openFail(false, ge);
+ openFail(ge);
throw ge;
}
}
- private void openFail(boolean invalidate, Exception cause) {
+ private void openFail(Exception cause) {
activeWindows = 0;
activeCopyRawData = 0;
- invalid = invalidate;
invalidatingCause = cause;
doClose();
}
@@ -764,11 +944,11 @@ public class Pack implements Iterable<PackIndex.MutableEntry> {
fd.seek(length - 20);
fd.readFully(buf, 0, 20);
if (!Arrays.equals(buf, packChecksum)) {
- throw new PackMismatchException(MessageFormat.format(
- JGitText.get().packChecksumMismatch,
- getPackFile(),
- ObjectId.fromRaw(buf).name(),
- ObjectId.fromRaw(idx.packChecksum).name()));
+ throw new PackMismatchException(
+ MessageFormat.format(JGitText.get().packChecksumMismatch,
+ getPackFile(), PackExt.PACK.getExtension(),
+ Hex.toHexString(buf), PackExt.INDEX.getExtension(),
+ Hex.toHexString(idx.getChecksum())));
}
}
@@ -1119,37 +1299,71 @@ public class Pack implements Iterable<PackIndex.MutableEntry> {
return getReverseIdx().findNextOffset(startOffset, maxOffset);
}
- synchronized PackBitmapIndex getBitmapIndex() throws IOException {
+ PackBitmapIndex getBitmapIndex() throws IOException {
+ Optional<PackBitmapIndex> optional = bitmapIdx.getOptional();
+ if (optional.isPresent()) {
+ return optional.get();
+ }
+ return memoizeBitmapIndexIfNeeded();
+ }
+
+ private synchronized PackBitmapIndex memoizeBitmapIndexIfNeeded() throws IOException {
if (invalid || bitmapIdxFile == null) {
return null;
}
- if (bitmapIdx == null) {
- final PackBitmapIndex idx;
- try {
- idx = PackBitmapIndex.open(bitmapIdxFile, idx(),
- getReverseIdx());
- } catch (FileNotFoundException e) {
- // Once upon a time this bitmap file existed. Now it
- // has been removed. Most likely an external gc has
- // removed this packfile and the bitmap
- bitmapIdxFile = null;
- return null;
- }
-
+ Optional<PackBitmapIndex> optional = bitmapIdx.getOptional();
+ if (optional.isPresent()) {
+ return optional.get();
+ }
+ try {
+ PackBitmapIndex idx = PackBitmapIndex.open(bitmapIdxFile, idx(),
+ getReverseIdx());
// At this point, idx() will have set packChecksum.
- if (Arrays.equals(packChecksum, idx.packChecksum)) {
- bitmapIdx = idx;
- } else {
- bitmapIdxFile = null;
+ if (Arrays.equals(packChecksum, idx.getPackChecksum())) {
+ bitmapIdx = optionally(idx);
+ return idx;
+ }
+ } catch (FileNotFoundException e) {
+ // Once upon a time the bitmap or index files existed. Now one
+ // of them has been removed. Most likely an external gc has
+ // removed index, packfile or the bitmap
+ } catch (IOException e) {
+ if (!FileUtils.isStaleFileHandleInCausalChain(e)) {
+ throw e;
}
+ // Ignore NFS stale handle exception the same way as
+ // FileNotFoundException above.
+ }
+ bitmapIdxFile = null;
+ return null;
+ }
+
+ void setBitmapIndexFile(PackFile bitmapIndexFile) {
+ this.bitmapIdxFile = bitmapIndexFile;
+ }
+
+ private PackReverseIndex getReverseIdx() throws IOException {
+ Optional<PackReverseIndex> optional = reverseIdx.getOptional();
+ if (optional.isPresent()) {
+ return optional.get();
}
- return bitmapIdx;
+ return memoizeReverseIdxIfNeeded();
}
- private synchronized PackReverseIndex getReverseIdx() throws IOException {
- if (reverseIdx == null)
- reverseIdx = new PackReverseIndex(idx());
- return reverseIdx;
+ private synchronized PackReverseIndex memoizeReverseIdxIfNeeded() throws IOException {
+ if (invalid) {
+ throw new PackInvalidException(packFile, invalidatingCause);
+ }
+ Optional<PackReverseIndex> optional = reverseIdx.getOptional();
+ if (optional.isPresent()) {
+ return optional.get();
+ }
+ PackFile reverseIndexFile = packFile.create(REVERSE_INDEX);
+ PackReverseIndex revIdx = PackReverseIndexFactory.openOrCompute(reverseIndexFile,
+ getObjectCount(), () -> getIndex());
+ revIdx.verifyPackChecksum(getPackFile().getPath());
+ reverseIdx = optionally(revIdx);
+ return revIdx;
}
private boolean isCorrupt(long offset) {
@@ -1184,4 +1398,8 @@ public class Pack implements Iterable<PackIndex.MutableEntry> {
+ packFile.length() + ", packChecksum="
+ ObjectId.fromRaw(packChecksum).name() + "]";
}
+
+ private <T> Optionally<T> optionally(T element) {
+ return useStrongRefs ? new Optionally.Hard<>(element) : new Optionally.Soft<>(element);
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java
index 8fb17fcf21..cbda8fc77c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndex.java
@@ -14,6 +14,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
+import java.util.function.Supplier;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.internal.JGitText;
@@ -34,7 +35,7 @@ import com.googlecode.javaewah.EWAHCompressedBitmap;
* {@link #findPosition(AnyObjectId)} can be used to build other bitmaps that a
* compatible with the encoded bitmaps available from the index.
*/
-public abstract class PackBitmapIndex {
+public interface PackBitmapIndex {
/** Flag bit denoting the bitmap should be reused during index creation. */
public static final int FLAG_REUSE = 1;
@@ -131,8 +132,14 @@ public abstract class PackBitmapIndex {
reverseIndexSupplier, loadParallelRevIndex);
}
- /** Footer checksum applied on the bottom of the pack file. */
- byte[] packChecksum;
+ /**
+ * Footer checksum applied on the bottom of the pack file.
+ *
+ * @return checksum as a byte array
+ */
+ default byte[] getPackChecksum() {
+ return null;
+ }
/**
* Finds the position in the bitmap of the object.
@@ -147,7 +154,9 @@ public abstract class PackBitmapIndex {
* Get the object at the bitmap position.
*
* @param position
- * the id for which the object will be found.
+ * the offset in the bitmap which corresponds to an object of
+ * interest. This position is the same as the order of the object
+ * in the {@link PackFile}.
* @return the ObjectId.
* @throws java.lang.IllegalArgumentException
* when the item is not found.
@@ -195,6 +204,41 @@ public abstract class PackBitmapIndex {
public abstract int getBitmapCount();
/**
+ * Returns the number of bitmaps in this bitmap index ready to use, not
+ * XOR'ed against other entries.
+ *
+ * @return the number of bitmaps in this bitmap index ready to use.
+ */
+ public abstract int getBaseBitmapCount();
+
+ /**
+ * Current size in bytes of all base bitmaps in the index.
+ *
+ * Resolving xors for bitmaps can affect this size.
+ *
+ * @return Current size (in bytes) of all base bitmaps in this index.
+ */
+ public abstract long getBaseBitmapSizeInBytes();
+
+ /**
+ * Returns the number of bitmaps in this bitmap index XOR'ed against other
+ * entries.
+ *
+ * @return the number of bitmaps in this bitmap index represented as XOR
+ * masks.
+ */
+ public abstract int getXorBitmapCount();
+
+ /**
+ * Current size in bytes of all XOR'ed bitmaps in the index.
+ *
+ * Resolving xors for bitmaps can affect this size.
+ *
+ * @return Current size (in bytes) of all xor bitmaps in this index.
+ */
+ public abstract long getXorBitmapSizeInBytes();
+
+ /**
* Supplier that propagates IOException.
*
* @param <T>
@@ -203,8 +247,13 @@ public abstract class PackBitmapIndex {
@FunctionalInterface
public interface SupplierWithIOException<T> {
/**
+ * Get result.
+ *
+ * @see Supplier#get()
+ *
* @return result
* @throws IOException
+ * if an IO error occurred
*/
T get() throws IOException;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java
index 5666b57609..08d2b7178f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexBuilder.java
@@ -11,9 +11,9 @@
package org.eclipse.jgit.internal.storage.file;
import java.text.MessageFormat;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.LinkedList;
import java.util.List;
import org.eclipse.jgit.internal.JGitText;
@@ -41,8 +41,7 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex {
private final EWAHCompressedBitmap tags;
private final BlockList<PositionEntry> byOffset;
- private final LinkedList<StoredBitmap>
- bitmapsToWriteXorBuffer = new LinkedList<>();
+ private final ArrayDeque<StoredBitmap> bitmapsToWriteXorBuffer = new ArrayDeque<>();
private List<StoredEntry> bitmapsToWrite = new ArrayList<>();
@@ -58,7 +57,7 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex {
* ObjectId (name); it will be resorted in place.
*/
public PackBitmapIndexBuilder(List<ObjectToPack> objects) {
- super(new ObjectIdOwnerMap<StoredBitmap>());
+ super(new ObjectIdOwnerMap<>());
byOffset = new BlockList<>(objects.size());
sortByOffsetAndIndex(byOffset, positionEntries, objects);
@@ -106,7 +105,7 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex {
.signum(a.getOffset() - b.getOffset()));
for (int i = 0; i < entries.size(); i++) {
PositionEntry e = positionEntries.get(entries.get(i));
- e.offsetPosition = i;
+ e.ridxPosition = i;
byOffset.add(e);
}
}
@@ -191,8 +190,8 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex {
throw new IllegalStateException();
}
bestBitmap.trim();
- StoredEntry result = new StoredEntry(entry.namePosition, bestBitmap,
- bestXorOffset, bitmapToWrite.getFlags());
+ StoredEntry result = new StoredEntry(entry, entry.idxPosition,
+ bestBitmap, bestXorOffset, bitmapToWrite.getFlags());
return result;
}
@@ -214,7 +213,6 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex {
getBitmaps().add(result);
}
- /** {@inheritDoc} */
@Override
public EWAHCompressedBitmap ofObjectType(
EWAHCompressedBitmap bitmap, int type) {
@@ -231,16 +229,14 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex {
throw new IllegalArgumentException();
}
- /** {@inheritDoc} */
@Override
public int findPosition(AnyObjectId objectId) {
PositionEntry entry = positionEntries.get(objectId);
if (entry == null)
return -1;
- return entry.offsetPosition;
+ return entry.ridxPosition;
}
- /** {@inheritDoc} */
@Override
public ObjectId getObject(int position) throws IllegalArgumentException {
ObjectId objectId = byOffset.get(position);
@@ -294,7 +290,6 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex {
return PackBitmapIndexV1.OPT_FULL;
}
- /** {@inheritDoc} */
@Override
public int getBitmapCount() {
return bitmapsToWriteXorBuffer.size() + bitmapsToWrite.size();
@@ -311,7 +306,6 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex {
bitmapsToWrite = new ArrayList<>(size);
}
- /** {@inheritDoc} */
@Override
public int getObjectCount() {
return byOffset.size();
@@ -328,54 +322,100 @@ public class PackBitmapIndexBuilder extends BasePackBitmapIndex {
generateStoredEntry(bitmapsToWriteXorBuffer.pollFirst()));
}
- Collections.reverse(bitmapsToWrite);
- return bitmapsToWrite;
+ List<StoredEntry> bitmapsToReturn = new ArrayList<>(bitmapsToWrite);
+ Collections.reverse(bitmapsToReturn);
+ return bitmapsToReturn;
}
/** Data object for the on disk representation of a bitmap entry. */
public static final class StoredEntry {
- private final long objectId;
+ private final ObjectId objectId;
+
+ private final long idxPosition;
+
private final EWAHCompressedBitmap bitmap;
+
private final int xorOffset;
+
private final int flags;
- StoredEntry(long objectId, EWAHCompressedBitmap bitmap,
- int xorOffset, int flags) {
+ /**
+ * Create a StoredEntry
+ *
+ * @param objectId
+ * objectId of the object associated with the bitmap
+ * @param idxPosition
+ * position of this object into the pack index (i.e. sorted
+ * by sha1)
+ * @param bitmap
+ * bitmap associated with this object
+ * @param xorOffset
+ * offset of the bitmap against which this bitmap is
+ * xor-compressed. If 0, then this bitmap is not
+ * xor-compressed against any other bitmap
+ * @param flags
+ * flags for this bitmap
+ */
+ public StoredEntry(ObjectId objectId, long idxPosition,
+ EWAHCompressedBitmap bitmap, int xorOffset, int flags) {
this.objectId = objectId;
+ this.idxPosition = idxPosition;
this.bitmap = bitmap;
this.xorOffset = xorOffset;
this.flags = flags;
}
- /** @return the bitmap */
+ /**
+ * Get the bitmap
+ *
+ * @return the bitmap
+ */
public EWAHCompressedBitmap getBitmap() {
return bitmap;
}
- /** @return the xorOffset */
+ /**
+ * Get the xorOffset
+ *
+ * @return the xorOffset
+ */
public int getXorOffset() {
return xorOffset;
}
- /** @return the flags */
+ /**
+ * Get the flags
+ *
+ * @return the flags
+ */
public int getFlags() {
return flags;
}
- /** @return the ObjectId */
- public long getObjectId() {
+ /**
+ * @return the position of the object with this bitmap in the primary
+ * index (i.e. ordered by sha1)
+ */
+ public long getIdxPosition() {
+ return idxPosition;
+ }
+
+ /**
+ * @return the objectId of the object associated with this bitmap
+ */
+ public ObjectId getObjectId() {
return objectId;
}
}
private static final class PositionEntry extends ObjectIdOwnerMap.Entry {
- final int namePosition;
+ final int idxPosition;
- int offsetPosition;
+ int ridxPosition;
- PositionEntry(AnyObjectId objectId, int namePosition) {
+ PositionEntry(AnyObjectId objectId, int idxPosition) {
super(objectId);
- this.namePosition = namePosition;
+ this.idxPosition = idxPosition;
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java
index e1b6f780c7..ffbc0737ac 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexRemapper.java
@@ -28,8 +28,8 @@ import com.googlecode.javaewah.IntIterator;
* implementations this implementation is not thread safe, as it is intended to
* be used with a PackBitmapIndexBuilder, which is also not thread safe.
*/
-public class PackBitmapIndexRemapper extends PackBitmapIndex
- implements Iterable<PackBitmapIndexRemapper.Entry> {
+public class PackBitmapIndexRemapper
+ implements PackBitmapIndex, Iterable<PackBitmapIndexRemapper.Entry> {
private final BasePackBitmapIndex oldPackIndex;
final PackBitmapIndex newPackIndex;
@@ -79,32 +79,47 @@ public class PackBitmapIndexRemapper extends PackBitmapIndex
oldPackIndex.getObject(pos));
}
- /** {@inheritDoc} */
@Override
public int findPosition(AnyObjectId objectId) {
return newPackIndex.findPosition(objectId);
}
- /** {@inheritDoc} */
@Override
public ObjectId getObject(int position) throws IllegalArgumentException {
return newPackIndex.getObject(position);
}
- /** {@inheritDoc} */
@Override
public int getObjectCount() {
return newPackIndex.getObjectCount();
}
- /** {@inheritDoc} */
+ @Override
+ public int getBaseBitmapCount() {
+ return newPackIndex.getBaseBitmapCount();
+ }
+
+ @Override
+ public long getBaseBitmapSizeInBytes() {
+ return newPackIndex.getBaseBitmapSizeInBytes();
+ }
+
+ @Override
+ public int getXorBitmapCount() {
+ return newPackIndex.getXorBitmapCount();
+ }
+
+ @Override
+ public long getXorBitmapSizeInBytes() {
+ return newPackIndex.getXorBitmapSizeInBytes();
+ }
+
@Override
public EWAHCompressedBitmap ofObjectType(
EWAHCompressedBitmap bitmap, int type) {
return newPackIndex.ofObjectType(bitmap, type);
}
- /** {@inheritDoc} */
@Override
public Iterator<Entry> iterator() {
if (oldPackIndex == null)
@@ -141,7 +156,6 @@ public class PackBitmapIndexRemapper extends PackBitmapIndex
};
}
- /** {@inheritDoc} */
@Override
public EWAHCompressedBitmap getBitmap(AnyObjectId objectId) {
EWAHCompressedBitmap bitmap = newPackIndex.getBitmap(objectId);
@@ -173,13 +187,16 @@ public class PackBitmapIndexRemapper extends PackBitmapIndex
this.flags = flags;
}
- /** @return the flags associated with the bitmap. */
+ /**
+ * Get flags
+ *
+ * @return the flags associated with the bitmap.
+ */
public int getFlags() {
return flags;
}
}
- /** {@inheritDoc} */
@Override
public int getBitmapCount() {
// The count is only useful for the end index, not the remapper.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java
index 988dc6c4ff..19608c1ce5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexV1.java
@@ -46,6 +46,8 @@ class PackBitmapIndexV1 extends BasePackBitmapIndex {
private static final int MAX_XOR_OFFSET = 126;
+ private byte[] packChecksum;
+
private static final ExecutorService executor = Executors
.newCachedThreadPool(new ThreadFactory() {
private final ThreadFactory baseFactory = Executors
@@ -84,7 +86,7 @@ class PackBitmapIndexV1 extends BasePackBitmapIndex {
throws IOException {
// An entry is object id, xor offset, flag byte, and a length encoded
// bitmap. The object id is an int32 of the nth position sorted by name.
- super(new ObjectIdOwnerMap<StoredBitmap>());
+ super(new ObjectIdOwnerMap<>());
this.bitmaps = getBitmaps();
// Optionally start loading reverse index in parallel to loading bitmap
@@ -214,7 +216,6 @@ class PackBitmapIndexV1 extends BasePackBitmapIndex {
this.reverseIndex = computedReverseIndex;
}
- /** {@inheritDoc} */
@Override
public int findPosition(AnyObjectId objectId) {
long offset = packIndex.findOffset(objectId);
@@ -223,7 +224,6 @@ class PackBitmapIndexV1 extends BasePackBitmapIndex {
return reverseIndex.findPosition(offset);
}
- /** {@inheritDoc} */
@Override
public ObjectId getObject(int position) throws IllegalArgumentException {
ObjectId objectId = reverseIndex.findObjectByPosition(position);
@@ -232,13 +232,11 @@ class PackBitmapIndexV1 extends BasePackBitmapIndex {
return objectId;
}
- /** {@inheritDoc} */
@Override
public int getObjectCount() {
return (int) packIndex.getObjectCount();
}
- /** {@inheritDoc} */
@Override
public EWAHCompressedBitmap ofObjectType(
EWAHCompressedBitmap bitmap, int type) {
@@ -255,13 +253,11 @@ class PackBitmapIndexV1 extends BasePackBitmapIndex {
throw new IllegalArgumentException();
}
- /** {@inheritDoc} */
@Override
public int getBitmapCount() {
return bitmaps.size();
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object o) {
// TODO(cranger): compare the pack checksum?
@@ -270,12 +266,16 @@ class PackBitmapIndexV1 extends BasePackBitmapIndex {
return false;
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
return getPackIndex().hashCode();
}
+ @Override
+ public byte[] getPackChecksum() {
+ return this.packChecksum;
+ }
+
PackIndex getPackIndex() {
return packIndex;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexWriterV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexWriterV1.java
index a5c8423dfd..38d7c90894 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexWriterV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackBitmapIndexWriterV1.java
@@ -19,6 +19,7 @@ import java.text.MessageFormat;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder.StoredEntry;
+import org.eclipse.jgit.internal.storage.pack.PackBitmapIndexWriter;
import org.eclipse.jgit.lib.Constants;
import com.googlecode.javaewah.EWAHCompressedBitmap;
@@ -28,7 +29,7 @@ import com.googlecode.javaewah.EWAHCompressedBitmap;
*
* @see PackBitmapIndexV1
*/
-public class PackBitmapIndexWriterV1 {
+public class PackBitmapIndexWriterV1 implements PackBitmapIndexWriter {
private final DigestOutputStream out;
private final DataOutput dataOutput;
@@ -60,6 +61,7 @@ public class PackBitmapIndexWriterV1 {
* an error occurred while writing to the output stream, or this
* index format cannot store the object data supplied.
*/
+ @Override
public void write(PackBitmapIndexBuilder bitmaps, byte[] packDataChecksum)
throws IOException {
if (bitmaps == null || packDataChecksum.length != 20)
@@ -113,7 +115,7 @@ public class PackBitmapIndexWriterV1 {
private void writeBitmapEntry(StoredEntry entry) throws IOException {
// Write object, XOR offset, and bitmap
- dataOutput.writeInt((int) entry.getObjectId());
+ dataOutput.writeInt((int) entry.getIdxPosition());
out.write(entry.getXorOffset());
out.write(entry.getFlags());
writeBitmap(entry.getBitmap());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java
index 6a99cb3d83..544961b936 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackDirectory.java
@@ -17,6 +17,8 @@ import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -24,6 +26,8 @@ import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -41,10 +45,12 @@ import org.eclipse.jgit.internal.storage.pack.PackWriter;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.CoreConfig.TrustStat;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.Iterators;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -65,11 +71,13 @@ class PackDirectory {
private static final PackList NO_PACKS = new PackList(FileSnapshot.DIRTY,
new Pack[0]);
+ private final Config config;
+
private final File directory;
private final AtomicReference<PackList> packList;
- private final boolean trustFolderStat;
+ private final TrustStat trustPackStat;
/**
* Initialize a reference to an on-disk 'pack' directory.
@@ -80,16 +88,10 @@ class PackDirectory {
* the location of the {@code pack} directory.
*/
PackDirectory(Config config, File directory) {
+ this.config = config;
this.directory = directory;
packList = new AtomicReference<>(NO_PACKS);
-
- // Whether to trust the pack folder's modification time. If set to false
- // we will always scan the .git/objects/pack folder to check for new
- // pack files. If set to true (default) we use the folder's size,
- // modification time, and key (inode) and assume that no new pack files
- // can be in this folder if these attributes have not changed.
- trustFolderStat = config.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
- ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
+ trustPackStat = config.get(CoreConfig.KEY).getTrustPackStat();
}
/**
@@ -108,22 +110,22 @@ class PackDirectory {
void close() {
PackList packs = packList.get();
if (packs != NO_PACKS && packList.compareAndSet(packs, NO_PACKS)) {
- for (Pack p : packs.packs) {
- p.close();
- }
+ Pack.close(Set.of(packs.packs));
}
}
Collection<Pack> getPacks() {
- PackList list = packList.get();
- if (list == NO_PACKS) {
- list = scanPacks(list);
- }
+ PackList list;
+ do {
+ list = packList.get();
+ if (list == NO_PACKS) {
+ list = scanPacks(list);
+ }
+ } while (searchPacksAgain(list));
Pack[] packs = list.packs;
return Collections.unmodifiableCollection(Arrays.asList(packs));
}
- /** {@inheritDoc} */
@Override
public String toString() {
return "PackDirectory[" + getDirectory() + "]"; //$NON-NLS-1$ //$NON-NLS-2$
@@ -275,12 +277,14 @@ class PackDirectory {
PackList pList = packList.get();
int retries = 0;
SEARCH: for (;;) {
- for (Pack p : pList.packs) {
+ for (Pack p : pList.inReverse()) {
try {
LocalObjectRepresentation rep = p.representation(curs, otp);
p.resetTransientErrorCount();
if (rep != null) {
- packer.select(otp, rep);
+ if (!packer.select(otp, rep)) {
+ return;
+ }
packer.checkSearchForReuseTimeout();
}
} catch (SearchForReuseTimeout e) {
@@ -309,38 +313,42 @@ class PackDirectory {
}
private void handlePackError(IOException e, Pack p) {
- String warnTmpl = null;
+ String warnTemplate = null;
+ String debugTemplate = null;
int transientErrorCount = 0;
- String errTmpl = JGitText.get().exceptionWhileReadingPack;
+ String errorTemplate = JGitText.get().exceptionWhileReadingPack;
if ((e instanceof CorruptObjectException)
|| (e instanceof PackInvalidException)) {
- warnTmpl = JGitText.get().corruptPack;
- LOG.warn(MessageFormat.format(warnTmpl,
+ warnTemplate = JGitText.get().corruptPack;
+ LOG.warn(MessageFormat.format(warnTemplate,
p.getPackFile().getAbsolutePath()), e);
// Assume the pack is corrupted, and remove it from the list.
remove(p);
} else if (e instanceof FileNotFoundException) {
if (p.getPackFile().exists()) {
- errTmpl = JGitText.get().packInaccessible;
+ errorTemplate = JGitText.get().packInaccessible;
transientErrorCount = p.incrementTransientErrorCount();
} else {
- warnTmpl = JGitText.get().packWasDeleted;
+ debugTemplate = JGitText.get().packWasDeleted;
remove(p);
}
} else if (FileUtils.isStaleFileHandleInCausalChain(e)) {
- warnTmpl = JGitText.get().packHandleIsStale;
+ warnTemplate = JGitText.get().packHandleIsStale;
remove(p);
} else {
transientErrorCount = p.incrementTransientErrorCount();
}
- if (warnTmpl != null) {
- LOG.warn(MessageFormat.format(warnTmpl,
+ if (warnTemplate != null) {
+ LOG.warn(MessageFormat.format(warnTemplate,
p.getPackFile().getAbsolutePath()), e);
+ } else if (debugTemplate != null) {
+ LOG.debug(MessageFormat.format(debugTemplate,
+ p.getPackFile().getAbsolutePath()), e);
} else {
if (doLogExponentialBackoff(transientErrorCount)) {
// Don't remove the pack from the list, as the error may be
// transient.
- LOG.error(MessageFormat.format(errTmpl,
+ LOG.error(MessageFormat.format(errorTemplate,
p.getPackFile().getAbsolutePath(),
Integer.valueOf(transientErrorCount)), e);
}
@@ -350,15 +358,33 @@ class PackDirectory {
/**
* @param n
* count of consecutive failures
- * @return @{code true} if i is a power of 2
+ * @return {@code true} if i is a power of 2
*/
private boolean doLogExponentialBackoff(int n) {
return (n & (n - 1)) == 0;
}
boolean searchPacksAgain(PackList old) {
- return ((!trustFolderStat) || old.snapshot.isModified(directory))
- && old != scanPacks(old);
+ switch (trustPackStat) {
+ case NEVER:
+ break;
+ case AFTER_OPEN:
+ try (InputStream stream = Files
+ .newInputStream(directory.toPath())) {
+ // open the pack directory to refresh attributes (on some NFS clients)
+ } catch (IOException e) {
+ // ignore
+ }
+ //$FALL-THROUGH$
+ case ALWAYS:
+ if (!old.snapshot.isModified(directory)) {
+ return false;
+ }
+ break;
+ case INHERIT:
+ // only used in CoreConfig internally
+ }
+ return old != scanPacks(old);
}
void insert(Pack pack) {
@@ -455,10 +481,14 @@ class PackDirectory {
&& !oldPack.getFileSnapshot().isModified(packFile)) {
forReuse.remove(packFile.getName());
list.add(oldPack);
+ PackFile bitMaps = packFilesByExt.get(BITMAP_INDEX);
+ if (bitMaps != null) {
+ oldPack.setBitmapIndexFile(bitMaps);
+ }
continue;
}
- list.add(new Pack(packFile, packFilesByExt.get(BITMAP_INDEX)));
+ list.add(new Pack(config, packFile, packFilesByExt.get(BITMAP_INDEX)));
foundNew = true;
}
@@ -472,9 +502,7 @@ class PackDirectory {
return old;
}
- for (Pack p : forReuse.values()) {
- p.close();
- }
+ Pack.close(new HashSet<>(forReuse.values()));
if (list.isEmpty()) {
return new PackList(snapshot, NO_PACKS.packs);
@@ -533,7 +561,7 @@ class PackDirectory {
for (String name : nameList) {
try {
PackFile pack = new PackFile(directory, name);
- if (pack.getPackExt() != null) {
+ if (pack.getPackExt() != null && !pack.isTmpGCFile()) {
Map<PackExt, PackFile> packByExt = packFilesByExtById
.get(pack.getId());
if (packByExt == null) {
@@ -560,5 +588,13 @@ class PackDirectory {
this.snapshot = monitor;
this.packs = packs;
}
+
+ Iterable<Pack> inReverse() {
+ return Iterators.iterable(reverseIterator());
+ }
+
+ Iterator<Pack> reverseIterator() {
+ return Iterators.reverseIterator(packs);
+ }
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
index 19979d0ed5..5f2015b834 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java
@@ -27,6 +27,7 @@ public class PackFile extends File {
private static final long serialVersionUID = 1L;
private static final String PREFIX = "pack-"; //$NON-NLS-1$
+ private static final String TMP_GC_PREFIX = ".tmp-"; //$NON-NLS-1$
private final String base; // PREFIX + id i.e.
// pack-0123456789012345678901234567890123456789
@@ -126,6 +127,13 @@ public class PackFile extends File {
}
/**
+ * @return whether the file is a temporary GC file
+ */
+ public boolean isTmpGCFile() {
+ return id.startsWith(TMP_GC_PREFIX);
+ }
+
+ /**
* Create a new similar PackFile with the given extension instead.
*
* @param ext
@@ -177,7 +185,11 @@ public class PackFile extends File {
private static PackExt getPackExt(String endsWithExtension) {
for (PackExt ext : PackExt.values()) {
- if (endsWithExtension.endsWith(ext.getExtension())) {
+ if (endsWithExtension.equals(ext.getExtension())) {
+ return ext;
+ }
+
+ if (endsWithExtension.equals("old-" + ext.getExtension())) {
return ext;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFileSnapshot.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFileSnapshot.java
index a784af8c3f..dbf7d8ae5d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFileSnapshot.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFileSnapshot.java
@@ -48,7 +48,6 @@ class PackFileSnapshot extends FileSnapshot {
this.checksum = checksum;
}
- /** {@inheritDoc} */
@Override
public boolean isModified(File packFile) {
if (!super.isModified(packFile)) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java
index f4f62d4205..b3e4efb4fc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java
@@ -42,8 +42,8 @@ import org.eclipse.jgit.util.io.SilentFileInputStream;
* by ObjectId.
* </p>
*/
-public abstract class PackIndex
- implements Iterable<PackIndex.MutableEntry>, ObjectIdSet {
+public interface PackIndex
+ extends Iterable<PackIndex.MutableEntry>, ObjectIdSet {
/**
* Open an existing pack <code>.idx</code> file for reading.
* <p>
@@ -61,10 +61,12 @@ public abstract class PackIndex
* the file exists but could not be read due to security errors,
* unrecognized data version, or unexpected data corruption.
*/
- public static PackIndex open(File idxFile) throws IOException {
+ static PackIndex open(File idxFile) throws IOException {
try (SilentFileInputStream fd = new SilentFileInputStream(
idxFile)) {
- return read(fd);
+ return read(fd);
+ } catch (FileNotFoundException e) {
+ throw e;
} catch (IOException ioe) {
throw new IOException(
MessageFormat.format(JGitText.get().unreadablePackIndex,
@@ -90,7 +92,7 @@ public abstract class PackIndex
* @throws org.eclipse.jgit.errors.CorruptObjectException
* the stream does not contain a valid pack index.
*/
- public static PackIndex read(InputStream fd) throws IOException,
+ static PackIndex read(InputStream fd) throws IOException,
CorruptObjectException {
final byte[] hdr = new byte[8];
IO.readFully(fd, hdr, 0, hdr.length);
@@ -107,16 +109,13 @@ public abstract class PackIndex
}
private static boolean isTOC(byte[] h) {
- final byte[] toc = PackIndexWriter.TOC;
+ final byte[] toc = BasePackIndexWriter.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.
*
@@ -124,13 +123,12 @@ public abstract class PackIndex
* the object to look for. Must not be null.
* @return true if the object is listed in this index; false otherwise.
*/
- public boolean hasObject(AnyObjectId id) {
+ default boolean hasObject(AnyObjectId id) {
return findOffset(id) != -1;
}
- /** {@inheritDoc} */
@Override
- public boolean contains(AnyObjectId id) {
+ default boolean contains(AnyObjectId id) {
return findOffset(id) != -1;
}
@@ -146,7 +144,7 @@ public abstract class PackIndex
* </p>
*/
@Override
- public abstract Iterator<MutableEntry> iterator();
+ Iterator<MutableEntry> iterator();
/**
* Obtain the total number of objects described by this index.
@@ -154,7 +152,7 @@ public abstract class PackIndex
* @return number of objects in this index, and likewise in the associated
* pack that this index was generated from.
*/
- public abstract long getObjectCount();
+ long getObjectCount();
/**
* Obtain the total number of objects needing 64 bit offsets.
@@ -162,7 +160,7 @@ public abstract class PackIndex
* @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.
*/
- public abstract long getOffset64Count();
+ long getOffset64Count();
/**
* Get ObjectId for the n-th object entry returned by {@link #iterator()}.
@@ -184,7 +182,7 @@ public abstract class PackIndex
* is 0, the second is 1, etc.
* @return the ObjectId for the corresponding entry.
*/
- public abstract ObjectId getObjectId(long nthPosition);
+ ObjectId getObjectId(long nthPosition);
/**
* Get ObjectId for the n-th object entry returned by {@link #iterator()}.
@@ -208,7 +206,7 @@ public abstract class PackIndex
* negative, but still valid.
* @return the ObjectId for the corresponding entry.
*/
- public final ObjectId getObjectId(int nthPosition) {
+ default ObjectId getObjectId(int nthPosition) {
if (nthPosition >= 0)
return getObjectId((long) nthPosition);
final int u31 = nthPosition >>> 1;
@@ -227,7 +225,7 @@ public abstract class PackIndex
* etc. Positions past 2**31-1 are negative, but still valid.
* @return the offset in a pack for the corresponding entry.
*/
- abstract long getOffset(long nthPosition);
+ long getOffset(long nthPosition);
/**
* Locate the file offset position for the requested object.
@@ -238,7 +236,7 @@ public abstract class PackIndex
* object does not exist in this index and is thus not stored in the
* associated pack.
*/
- public abstract long findOffset(AnyObjectId objId);
+ long findOffset(AnyObjectId objId);
/**
* Locate the position of this id in the list of object-ids in the index
@@ -249,7 +247,7 @@ public abstract class PackIndex
* of ids stored in this index; -1 if the object does not exist in
* this index and is thus not stored in the associated pack.
*/
- public abstract int findPosition(AnyObjectId objId);
+ int findPosition(AnyObjectId objId);
/**
* Retrieve stored CRC32 checksum of the requested object raw-data
@@ -263,7 +261,7 @@ public abstract class PackIndex
* @throws java.lang.UnsupportedOperationException
* when this index doesn't support CRC32 checksum
*/
- public abstract long findCRC32(AnyObjectId objId)
+ long findCRC32(AnyObjectId objId)
throws MissingObjectException, UnsupportedOperationException;
/**
@@ -271,7 +269,7 @@ public abstract class PackIndex
*
* @return true if CRC32 is stored, false otherwise
*/
- public abstract boolean hasCRC32Support();
+ boolean hasCRC32Support();
/**
* Find objects matching the prefix abbreviation.
@@ -287,25 +285,27 @@ public abstract class PackIndex
* @throws java.io.IOException
* the index cannot be read.
*/
- public abstract void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
- int matchLimit) throws IOException;
+ void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
+ int matchLimit) throws IOException;
/**
+ * Get pack checksum
+ *
* @return the checksum of the pack; caller must not modify it
* @since 5.5
*/
- public byte[] getChecksum() {
- return packChecksum;
- }
+ byte[] getChecksum();
/**
* Represent mutable entry of pack index consisting of object id and offset
* in pack (both mutable).
*
*/
- public static class MutableEntry {
+ class MutableEntry {
+ /** Buffer of the ObjectId visited by the EntriesIterator. */
final MutableObjectId idBuffer = new MutableObjectId();
+ /** Offset into the packfile of the current object. */
long offset;
/**
@@ -317,42 +317,89 @@ public abstract class PackIndex
return offset;
}
- /** @return hex string describing the object id of this entry. */
+ /**
+ * Get hex string representation of the entry's object id
+ *
+ * @return hex string describing the object id of this entry.
+ */
public String name() {
- ensureId();
return idBuffer.name();
}
- /** @return a copy of the object id. */
+ /**
+ * Create a copy of the object id
+ *
+ * @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 */
+ /**
+ * Clone the entry
+ *
+ * @return a complete copy of this entry, that won't modify
+ */
public MutableEntry cloneEntry() {
final MutableEntry r = new MutableEntry();
- ensureId();
r.idBuffer.fromObjectId(idBuffer);
r.offset = offset;
return r;
}
- void ensureId() {
- // Override in implementations.
+ /**
+ * Similar to {@link Comparable#compareTo(Object)}, using only the
+ * object id in the entry.
+ *
+ * @param other
+ * Another mutable entry (probably from another index)
+ *
+ * @return a negative integer, zero, or a positive integer as this
+ * object is less than, equal to, or greater than the specified
+ * object.
+ */
+ public int compareBySha1To(MutableEntry other) {
+ return idBuffer.compareTo(other.idBuffer);
+ }
+
+ /**
+ * Copy the current ObjectId to dest
+ * <p>
+ * Like {@link #toObjectId()}, but reusing the destination instead of
+ * creating a new ObjectId instance.
+ *
+ * @param dest
+ * destination for the object id
+ */
+ public void copyOidTo(MutableObjectId dest) {
+ dest.fromObjectId(idBuffer);
}
}
+ /**
+ * Base implementation of the iterator over index entries.
+ */
abstract class EntriesIterator implements Iterator<MutableEntry> {
- protected final MutableEntry entry = initEntry();
+ private final long objectCount;
- protected long returnedNumber = 0;
+ private final MutableEntry entry = new MutableEntry();
- protected abstract MutableEntry initEntry();
+ /** Counts number of entries accessed so far. */
+ private long returnedNumber = 0;
+
+ /**
+ * Construct an iterator that can move objectCount times forward.
+ *
+ * @param objectCount
+ * the number of objects in the PackFile.
+ */
+ protected EntriesIterator(long objectCount) {
+ this.objectCount = objectCount;
+ }
@Override
public boolean hasNext() {
- return returnedNumber < getObjectCount();
+ return returnedNumber < objectCount;
}
/**
@@ -360,7 +407,55 @@ public abstract class PackIndex
* element.
*/
@Override
- public abstract MutableEntry next();
+ public MutableEntry next() {
+ readNext();
+ returnedNumber++;
+ return entry;
+ }
+
+ /**
+ * Used by subclasses to load the next entry into the MutableEntry.
+ * <p>
+ * Subclasses are expected to populate the entry with
+ * {@link #setIdBuffer} and {@link #setOffset}.
+ */
+ protected abstract void readNext();
+
+ /**
+ * Copies to the entry an {@link ObjectId} from the int buffer and
+ * position idx
+ *
+ * @param raw
+ * the raw data
+ * @param idx
+ * the index into {@code raw}
+ */
+ protected void setIdBuffer(int[] raw, int idx) {
+ entry.idBuffer.fromRaw(raw, idx);
+ }
+
+ /**
+ * Copies to the entry an {@link ObjectId} from the byte array at
+ * position idx.
+ *
+ * @param raw
+ * the raw data
+ * @param idx
+ * the index into {@code raw}
+ */
+ protected void setIdBuffer(byte[] raw, int idx) {
+ entry.idBuffer.fromRaw(raw, idx);
+ }
+
+ /**
+ * Sets the {@code offset} to the entry
+ *
+ * @param offset
+ * the offset in the pack file
+ */
+ protected void setOffset(long offset) {
+ entry.offset = offset;
+ }
@Override
public void remove() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
index fff410b4ce..be48358a0d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
@@ -29,13 +29,16 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.NB;
-class PackIndexV1 extends PackIndex {
+class PackIndexV1 implements PackIndex {
private static final int IDX_HDR_LEN = 256 * 4;
private static final int RECORD_SIZE = 4 + Constants.OBJECT_ID_LENGTH;
private final long[] idxHeader;
+ /** Footer checksum applied on the bottom of the pack file. */
+ protected byte[] packChecksum;
+
byte[][] idxdata;
private long objectCnt;
@@ -72,13 +75,11 @@ class PackIndexV1 extends PackIndex {
IO.readFully(fd, packChecksum, 0, packChecksum.length);
}
- /** {@inheritDoc} */
@Override
public long getObjectCount() {
return objectCnt;
}
- /** {@inheritDoc} */
@Override
public long getOffset64Count() {
long n64 = 0;
@@ -111,7 +112,6 @@ class PackIndexV1 extends PackIndex {
return (int) (nthPosition - base);
}
- /** {@inheritDoc} */
@Override
public ObjectId getObjectId(long nthPosition) {
final int levelOne = findLevelOne(nthPosition);
@@ -121,14 +121,13 @@ class PackIndexV1 extends PackIndex {
}
@Override
- long getOffset(long nthPosition) {
+ public long getOffset(long nthPosition) {
final int levelOne = findLevelOne(nthPosition);
final int levelTwo = getLevelTwo(nthPosition, levelOne);
final int p = (4 + Constants.OBJECT_ID_LENGTH) * levelTwo;
return NB.decodeUInt32(idxdata[levelOne], p);
}
- /** {@inheritDoc} */
@Override
public long findOffset(AnyObjectId objId) {
final int levelOne = objId.getFirstByte();
@@ -142,10 +141,9 @@ class PackIndexV1 extends PackIndex {
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);
+ return (((long) b0) << 24) | (b1 << 16) | (b2 << 8) | b3;
}
- /** {@inheritDoc} */
@Override
public int findPosition(AnyObjectId objId) {
int levelOne = objId.getFirstByte();
@@ -193,25 +191,21 @@ class PackIndexV1 extends PackIndex {
return -1;
}
- /** {@inheritDoc} */
@Override
public long findCRC32(AnyObjectId objId) {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public boolean hasCRC32Support() {
return false;
}
- /** {@inheritDoc} */
@Override
public Iterator<MutableEntry> iterator() {
- return new IndexV1Iterator();
+ return new EntriesIteratorV1(this);
}
- /** {@inheritDoc} */
@Override
public void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
int matchLimit) throws IOException {
@@ -247,32 +241,35 @@ class PackIndexV1 extends PackIndex {
return (RECORD_SIZE * mid) + 4;
}
- private class IndexV1Iterator extends EntriesIterator {
- int levelOne;
+ @Override
+ public byte[] getChecksum() {
+ return packChecksum;
+ }
- int levelTwo;
+ private static class EntriesIteratorV1 extends EntriesIterator {
+ private int levelOne;
- @Override
- protected MutableEntry initEntry() {
- return new MutableEntry() {
- @Override
- protected void ensureId() {
- idBuffer.fromRaw(idxdata[levelOne], levelTwo
- - Constants.OBJECT_ID_LENGTH);
- }
- };
+ private int levelTwo;
+
+ private final PackIndexV1 packIndex;
+
+ private EntriesIteratorV1(PackIndexV1 packIndex) {
+ super(packIndex.objectCnt);
+ this.packIndex = packIndex;
}
@Override
- public MutableEntry next() {
- for (; levelOne < idxdata.length; levelOne++) {
- if (idxdata[levelOne] == null)
+ protected void readNext() {
+ for (; levelOne < packIndex.idxdata.length; levelOne++) {
+ if (packIndex.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;
+ if (levelTwo < packIndex.idxdata[levelOne].length) {
+ super.setOffset(NB.decodeUInt32(packIndex.idxdata[levelOne],
+ levelTwo));
+ this.levelTwo += Constants.OBJECT_ID_LENGTH + 4;
+ super.setIdBuffer(packIndex.idxdata[levelOne],
+ levelTwo - Constants.OBJECT_ID_LENGTH);
+ return;
}
levelTwo = 0;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java
index 7a390060c7..36e54fcd62 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java
@@ -28,7 +28,7 @@ import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.NB;
/** Support for the pack index v2 format. */
-class PackIndexV2 extends PackIndex {
+class PackIndexV2 implements PackIndex {
private static final long IS_O64 = 1L << 31;
private static final int FANOUT = 256;
@@ -37,6 +37,9 @@ class PackIndexV2 extends PackIndex {
private static final byte[] NO_BYTES = {};
+ /** Footer checksum applied on the bottom of the pack file. */
+ protected byte[] packChecksum;
+
private long objectCnt;
private final long[] fanoutTable;
@@ -131,13 +134,11 @@ class PackIndexV2 extends PackIndex {
IO.readFully(fd, packChecksum, 0, packChecksum.length);
}
- /** {@inheritDoc} */
@Override
public long getObjectCount() {
return objectCnt;
}
- /** {@inheritDoc} */
@Override
public long getOffset64Count() {
return offset64.length / 8;
@@ -165,7 +166,6 @@ class PackIndexV2 extends PackIndex {
return (int) (nthPosition - base);
}
- /** {@inheritDoc} */
@Override
public ObjectId getObjectId(long nthPosition) {
final int levelOne = findLevelOne(nthPosition);
@@ -174,7 +174,6 @@ class PackIndexV2 extends PackIndex {
return ObjectId.fromRaw(names[levelOne], p4 + p); // p * 5
}
- /** {@inheritDoc} */
@Override
public long getOffset(long nthPosition) {
final int levelOne = findLevelOne(nthPosition);
@@ -182,7 +181,6 @@ class PackIndexV2 extends PackIndex {
return getOffset(levelOne, levelTwo);
}
- /** {@inheritDoc} */
@Override
public long findOffset(AnyObjectId objId) {
final int levelOne = objId.getFirstByte();
@@ -192,7 +190,6 @@ class PackIndexV2 extends PackIndex {
return getOffset(levelOne, levelTwo);
}
- /** {@inheritDoc} */
@Override
public int findPosition(AnyObjectId objId) {
int levelOne = objId.getFirstByte();
@@ -211,7 +208,6 @@ class PackIndexV2 extends PackIndex {
return p;
}
- /** {@inheritDoc} */
@Override
public long findCRC32(AnyObjectId objId) throws MissingObjectException {
final int levelOne = objId.getFirstByte();
@@ -221,19 +217,16 @@ class PackIndexV2 extends PackIndex {
return NB.decodeUInt32(crc32[levelOne], levelTwo << 2);
}
- /** {@inheritDoc} */
@Override
public boolean hasCRC32Support() {
return true;
}
- /** {@inheritDoc} */
@Override
public Iterator<MutableEntry> iterator() {
- return new EntriesIteratorV2();
+ return new EntriesIteratorV2(this);
}
- /** {@inheritDoc} */
@Override
public void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
int matchLimit) throws IOException {
@@ -291,37 +284,39 @@ class PackIndexV2 extends PackIndex {
return -1;
}
- private class EntriesIteratorV2 extends EntriesIterator {
- int levelOne;
+ @Override
+ public byte[] getChecksum() {
+ return packChecksum;
+ }
- int levelTwo;
+ private static class EntriesIteratorV2 extends EntriesIterator {
+ private int levelOne = 0;
- @Override
- protected MutableEntry initEntry() {
- return new MutableEntry() {
- @Override
- protected void ensureId() {
- idBuffer.fromRaw(names[levelOne], levelTwo
- - Constants.OBJECT_ID_LENGTH / 4);
- }
- };
+ private int levelTwo = 0;
+
+ private final PackIndexV2 packIndex;
+
+ private EntriesIteratorV2(PackIndexV2 packIndex) {
+ super(packIndex.objectCnt);
+ this.packIndex = packIndex;
}
@Override
- public MutableEntry next() {
- for (; levelOne < names.length; levelOne++) {
- if (levelTwo < names[levelOne].length) {
+ protected void readNext() {
+ for (; levelOne < packIndex.names.length; levelOne++) {
+ if (levelTwo < packIndex.names[levelOne].length) {
int idx = levelTwo / (Constants.OBJECT_ID_LENGTH / 4) * 4;
- long offset = NB.decodeUInt32(offset32[levelOne], idx);
+ long offset = NB.decodeUInt32(packIndex.offset32[levelOne],
+ idx);
if ((offset & IS_O64) != 0) {
idx = (8 * (int) (offset & ~IS_O64));
- offset = NB.decodeUInt64(offset64, idx);
+ offset = NB.decodeUInt64(packIndex.offset64, idx);
}
- entry.offset = offset;
-
- levelTwo += Constants.OBJECT_ID_LENGTH / 4;
- returnedNumber++;
- return entry;
+ super.setOffset(offset);
+ this.levelTwo += Constants.OBJECT_ID_LENGTH / 4;
+ super.setIdBuffer(packIndex.names[levelOne],
+ levelTwo - Constants.OBJECT_ID_LENGTH / 4);
+ return;
}
levelTwo = 0;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV1.java
index e1612bb579..f0b6193066 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV1.java
@@ -21,10 +21,10 @@ import org.eclipse.jgit.util.NB;
/**
* Creates the version 1 (old style) pack table of contents files.
*
- * @see PackIndexWriter
+ * @see BasePackIndexWriter
* @see PackIndexV1
*/
-class PackIndexWriterV1 extends PackIndexWriter {
+class PackIndexWriterV1 extends BasePackIndexWriter {
static boolean canStore(PackedObjectInfo oe) {
// We are limited to 4 GB per pack as offset is 32 bit unsigned int.
//
@@ -35,7 +35,6 @@ class PackIndexWriterV1 extends PackIndexWriter {
super(dst);
}
- /** {@inheritDoc} */
@Override
protected void writeImpl() throws IOException {
writeFanOutTable();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV2.java
index 7adabad708..b72b35a464 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV2.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV2.java
@@ -19,10 +19,10 @@ import org.eclipse.jgit.util.NB;
/**
* Creates the version 2 pack table of contents files.
*
- * @see PackIndexWriter
+ * @see BasePackIndexWriter
* @see PackIndexV2
*/
-class PackIndexWriterV2 extends PackIndexWriter {
+class PackIndexWriterV2 extends BasePackIndexWriter {
private static final int MAX_OFFSET_32 = 0x7fffffff;
private static final int IS_OFFSET_64 = 0x80000000;
@@ -30,7 +30,6 @@ class PackIndexWriterV2 extends PackIndexWriter {
super(dst);
}
- /** {@inheritDoc} */
@Override
protected void writeImpl() throws IOException {
writeTOC(2);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java
index 0bceca72ea..fdc2f80075 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInputStream.java
@@ -31,7 +31,6 @@ class PackInputStream extends InputStream {
wc.pin(pack, pos);
}
- /** {@inheritDoc} */
@Override
public int read(byte[] b, int off, int len) throws IOException {
int n = wc.copy(pack, pos, b, off, len);
@@ -39,7 +38,6 @@ class PackInputStream extends InputStream {
return n;
}
- /** {@inheritDoc} */
@Override
public int read() throws IOException {
byte[] buf = new byte[1];
@@ -47,7 +45,6 @@ class PackInputStream extends InputStream {
return n == 1 ? buf[0] & 0xff : -1;
}
- /** {@inheritDoc} */
@Override
public void close() {
wc.close();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java
index d6209c4a79..97a854b8cd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java
@@ -77,6 +77,7 @@ import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.internal.storage.pack.PackIndexWriter;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
@@ -150,7 +151,6 @@ public class PackInserter extends ObjectInserter {
return buffer().length;
}
- /** {@inheritDoc} */
@Override
public ObjectId insert(int type, byte[] data, int off, int len)
throws IOException {
@@ -166,10 +166,9 @@ public class PackInserter extends ObjectInserter {
long offset = beginObject(type, len);
packOut.compress.write(data, off, len);
packOut.compress.finish();
- return endObject(id, offset);
+ return endObject(id, offset, len, type);
}
- /** {@inheritDoc} */
@Override
public ObjectId insert(int type, long len, InputStream in)
throws IOException {
@@ -196,7 +195,7 @@ public class PackInserter extends ObjectInserter {
len -= n;
}
packOut.compress.finish();
- return endObject(md.toObjectId(), offset);
+ return endObject(md.toObjectId(), offset, len, type);
}
private long beginObject(int type, long len) throws IOException {
@@ -208,10 +207,12 @@ public class PackInserter extends ObjectInserter {
return offset;
}
- private ObjectId endObject(ObjectId id, long offset) {
+ private ObjectId endObject(ObjectId id, long offset, long len, int type) {
PackedObjectInfo obj = new PackedObjectInfo(id);
+ obj.setType(type);
obj.setOffset(offset);
obj.setCRC((int) packOut.crc32.getValue());
+ obj.setFullSize(len);
objectList.add(obj);
objectMap.addIfAbsent(obj);
return id;
@@ -224,6 +225,12 @@ public class PackInserter extends ObjectInserter {
p.substring(0, p.lastIndexOf('.')) + ".idx"); //$NON-NLS-1$
}
+ private static File getFileFor(File packFile, PackExt ext) {
+ String p = packFile.getName();
+ return new File(packFile.getParentFile(),
+ p.substring(0, p.lastIndexOf('.')) + ext.getExtension());
+ }
+
private void beginPack() throws IOException {
objectList = new BlockList<>();
objectMap = new ObjectIdOwnerMap<>();
@@ -243,19 +250,16 @@ public class PackInserter extends ObjectInserter {
return 12;
}
- /** {@inheritDoc} */
@Override
public PackParser newPackParser(InputStream in) {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public ObjectReader newReader() {
return new Reader();
}
- /** {@inheritDoc} */
@Override
public void flush() throws IOException {
if (tmpPack == null) {
@@ -276,7 +280,11 @@ public class PackInserter extends ObjectInserter {
Collections.sort(objectList);
File tmpIdx = idxFor(tmpPack); // TODO(nasserg) Use PackFile?
writePackIndex(tmpIdx, packHash, objectList);
-
+ File tmpObjSizeIdx = null;
+ if (pconfig.isWriteObjSizeIndex()) {
+ tmpObjSizeIdx = getFileFor(tmpPack, PackExt.OBJECT_SIZE_INDEX);
+ writeObjectSizeIndex(tmpObjSizeIdx, objectList, pconfig);
+ }
PackFile realPack = new PackFile(db.getPackDirectory(),
computeName(objectList), PackExt.PACK);
db.closeAllPackHandles(realPack);
@@ -301,6 +309,13 @@ public class PackInserter extends ObjectInserter {
realIdx), e);
}
+ if (tmpObjSizeIdx != null) {
+ PackFile realObjSizeIdx = realPack
+ .create(PackExt.OBJECT_SIZE_INDEX);
+ tmpObjSizeIdx.setReadOnly();
+ FileUtils.rename(tmpObjSizeIdx, realObjSizeIdx, ATOMIC_MOVE);
+ }
+
boolean interrupted = false;
try {
FileSnapshot snapshot = FileSnapshot.save(realPack);
@@ -325,11 +340,21 @@ public class PackInserter extends ObjectInserter {
private static void writePackIndex(File idx, byte[] packHash,
List<PackedObjectInfo> list) throws IOException {
try (OutputStream os = new FileOutputStream(idx)) {
- PackIndexWriter w = PackIndexWriter.createVersion(os, INDEX_VERSION);
+ PackIndexWriter w = BasePackIndexWriter.createVersion(os,
+ INDEX_VERSION);
w.write(list, packHash);
}
}
+ private static void writeObjectSizeIndex(File objIdx,
+ List<PackedObjectInfo> list, PackConfig cfg) throws IOException {
+ try (OutputStream os = new FileOutputStream(objIdx)) {
+ PackObjectSizeIndexWriter w = PackObjectSizeIndexWriter
+ .createWriter(os, cfg.getMinBytesForObjSizeIndex());
+ w.write(list);
+ }
+ }
+
private ObjectId computeName(List<PackedObjectInfo> list) {
SHA1 md = digest().reset();
byte[] buf = buffer();
@@ -340,7 +365,6 @@ public class PackInserter extends ObjectInserter {
return ObjectId.fromRaw(md.digest());
}
- /** {@inheritDoc} */
@Override
public void close() {
try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexHelper.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexHelper.java
new file mode 100644
index 0000000000..acef741fc6
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexHelper.java
@@ -0,0 +1,92 @@
+package org.eclipse.jgit.internal.storage.file;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.transport.PackedObjectInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A wrapper to write the object size index for existing packs (on disk)
+ */
+public class PackObjectSizeIndexHelper {
+
+ private final static Logger LOG = LoggerFactory
+ .getLogger(PackObjectSizeIndexHelper.class);
+
+ /**
+ * Add an object size index to all the packs without one in this object
+ * database.
+ *
+ * @param db
+ * object db for packs
+ * @param pm
+ * progress monitor for the object listing phase (adding size to
+ * the objects in index)
+ * @throws IOException
+ * an error reading the packs or writing the object size index
+ * file
+ */
+ public static void forAllPacks(ObjectDirectory db, ProgressMonitor pm)
+ throws IOException {
+ WindowCursor wc = (WindowCursor) db.newReader();
+ for (Pack pack : db.getPacks()) {
+ LOG.info("Checking " + pack.getPackName()); //$NON-NLS-1$
+ if (pack.hasObjectSizeIndex()) {
+ LOG.debug(" has object size index"); //$NON-NLS-1$
+ continue;
+ }
+
+ List<PackedObjectInfo> objectsInPack = getObjectsInPack(wc, pack,
+ pm);
+ LOG.debug(String.format(" index has %d objects", //$NON-NLS-1$
+ objectsInPack.size()));
+ if (objectsInPack.isEmpty()) {
+ continue;
+ }
+
+ LOG.info(" start writing object size index"); //$NON-NLS-1$
+ PackFile packFile = pack.getPackFile()
+ .create(PackExt.OBJECT_SIZE_INDEX);
+ long start = System.currentTimeMillis();
+ try (FileOutputStream out = new FileOutputStream(packFile)) {
+ PackObjectSizeIndexWriter writer = PackObjectSizeIndexWriter
+ .createWriter(out, 0);
+ writer.write(objectsInPack);
+ }
+ LOG.info(String.format(" done writing. Took %d ms", //$NON-NLS-1$
+ System.currentTimeMillis() - start));
+ }
+ }
+
+ private static List<PackedObjectInfo> getObjectsInPack(WindowCursor wc,
+ Pack pack, ProgressMonitor pm) throws IOException {
+ PackIndex idx = pack.getIndex();
+ PackReverseIndex ridx = new PackReverseIndexComputed(idx);
+ pm.beginTask("Adding size to objects in index", //$NON-NLS-1$
+ (int) idx.getObjectCount());
+ // This is much faster in offset order
+ List<PackedObjectInfo> objs = new ArrayList<>(
+ (int) idx.getObjectCount());
+ for (int i = 0; i < idx.getObjectCount(); i++) {
+ ObjectId oid = ridx.findObjectByPosition(i);
+ PackedObjectInfo poi = new PackedObjectInfo(oid);
+ long offset = idx.findOffset(oid);
+ poi.setFullSize(pack.getObjectSize(wc, offset));
+ poi.setType(pack.getObjectType(wc, offset));
+ objs.add(poi);
+ pm.update(1);
+ }
+ pm.endTask();
+ return objs;
+ }
+
+ private PackObjectSizeIndexHelper() {
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexLoader.java
index 9d6941823a..dff9fa52c8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexLoader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexLoader.java
@@ -11,7 +11,9 @@ package org.eclipse.jgit.internal.storage.file;
import java.io.IOException;
import java.io.InputStream;
+import java.text.MessageFormat;
import java.util.Arrays;
+import org.eclipse.jgit.internal.JGitText;
/**
* Chooses the specific implementation of the object-size index based on the
@@ -26,17 +28,23 @@ public class PackObjectSizeIndexLoader {
* input stream at the beginning of the object size data
* @return an implementation of the object size index
* @throws IOException
- * error reading the streams
+ * error reading the stream, empty stream or content is not an
+ * object size index
*/
public static PackObjectSizeIndex load(InputStream in) throws IOException {
byte[] header = in.readNBytes(4);
if (!Arrays.equals(header, PackObjectSizeIndexWriter.HEADER)) {
- throw new IOException("Stream is not an object index"); //$NON-NLS-1$
+ throw new IOException(MessageFormat.format(
+ JGitText.get().unreadableObjectSizeIndex,
+ Integer.valueOf(header.length),
+ Arrays.toString(header)));
}
int version = in.readNBytes(1)[0];
if (version != 1) {
- throw new IOException("Unknown object size version: " + version); //$NON-NLS-1$
+ throw new IOException(MessageFormat.format(
+ JGitText.get().unsupportedObjectSizeIndexVersion,
+ Integer.valueOf(version)));
}
return PackObjectSizeIndexV1.parse(in);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexV1.java
index be2ff67e4f..9957f54fbf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexV1.java
@@ -12,7 +12,7 @@ package org.eclipse.jgit.internal.storage.file;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
-import java.util.Arrays;
+import java.text.MessageFormat;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.util.NB;
@@ -35,7 +35,7 @@ class PackObjectSizeIndexV1 implements PackObjectSizeIndex {
private final UInt24Array positions24;
- private final int[] positions32;
+ private final IntArray positions32;
/**
* Parallel array to concat(positions24, positions32) with the size of the
@@ -45,35 +45,37 @@ class PackObjectSizeIndexV1 implements PackObjectSizeIndex {
* doesn't fit in an int and |value|-1 is the position for the size in the
* size64 array e.g. a value of -1 is sizes64[0], -2 = sizes64[1], ...
*/
- private final int[] sizes32;
+ private final IntArray sizes32;
- private final long[] sizes64;
+ private final LongArray sizes64;
static PackObjectSizeIndex parse(InputStream in) throws IOException {
/** Header and version already out of the input */
- IndexInputStreamReader stream = new IndexInputStreamReader(in);
- int threshold = stream.readInt(); // minSize
- int objCount = stream.readInt();
+ byte[] buffer = new byte[8];
+ in.readNBytes(buffer, 0, 8);
+ int threshold = NB.decodeInt32(buffer, 0); // minSize
+ int objCount = NB.decodeInt32(buffer, 4);
if (objCount == 0) {
return new EmptyPackObjectSizeIndex(threshold);
}
- return new PackObjectSizeIndexV1(stream, threshold, objCount);
+ return new PackObjectSizeIndexV1(in, threshold, objCount);
}
- private PackObjectSizeIndexV1(IndexInputStreamReader stream, int threshold,
+ private PackObjectSizeIndexV1(InputStream stream, int threshold,
int objCount) throws IOException {
this.threshold = threshold;
UInt24Array pos24 = null;
- int[] pos32 = null;
+ IntArray pos32 = null;
+ StreamHelper helper = new StreamHelper();
byte positionEncoding;
- while ((positionEncoding = stream.readByte()) != 0) {
+ while ((positionEncoding = helper.readByte(stream)) != 0) {
if (Byte.compareUnsigned(positionEncoding, BITS_24) == 0) {
- int sz = stream.readInt();
+ int sz = helper.readInt(stream);
pos24 = new UInt24Array(stream.readNBytes(sz * 3));
} else if (Byte.compareUnsigned(positionEncoding, BITS_32) == 0) {
- int sz = stream.readInt();
- pos32 = stream.readIntArray(sz);
+ int sz = helper.readInt(stream);
+ pos32 = IntArray.from(stream, sz);
} else {
throw new UnsupportedEncodingException(
String.format(JGitText.get().unknownPositionEncoding,
@@ -81,16 +83,16 @@ class PackObjectSizeIndexV1 implements PackObjectSizeIndex {
}
}
positions24 = pos24 != null ? pos24 : UInt24Array.EMPTY;
- positions32 = pos32 != null ? pos32 : new int[0];
+ positions32 = pos32 != null ? pos32 : IntArray.EMPTY;
- sizes32 = stream.readIntArray(objCount);
- int c64sizes = stream.readInt();
+ sizes32 = IntArray.from(stream, objCount);
+ int c64sizes = helper.readInt(stream);
if (c64sizes == 0) {
- sizes64 = new long[0];
+ sizes64 = LongArray.EMPTY;
return;
}
- sizes64 = stream.readLongArray(c64sizes);
- int c128sizes = stream.readInt();
+ sizes64 = LongArray.from(stream, c64sizes);
+ int c128sizes = helper.readInt(stream);
if (c128sizes != 0) {
// this MUST be 0 (we don't support 128 bits sizes yet)
throw new IOException(JGitText.get().unsupportedSizesObjSizeIndex);
@@ -102,8 +104,8 @@ class PackObjectSizeIndexV1 implements PackObjectSizeIndex {
int pos = -1;
if (!positions24.isEmpty() && idxOffset <= positions24.getLastValue()) {
pos = positions24.binarySearch(idxOffset);
- } else if (positions32.length > 0 && idxOffset >= positions32[0]) {
- int pos32 = Arrays.binarySearch(positions32, idxOffset);
+ } else if (!positions32.empty() && idxOffset >= positions32.get(0)) {
+ int pos32 = positions32.binarySearch(idxOffset);
if (pos32 >= 0) {
pos = pos32 + positions24.size();
}
@@ -112,17 +114,17 @@ class PackObjectSizeIndexV1 implements PackObjectSizeIndex {
return -1;
}
- int objSize = sizes32[pos];
+ int objSize = sizes32.get(pos);
if (objSize < 0) {
int secondPos = Math.abs(objSize) - 1;
- return sizes64[secondPos];
+ return sizes64.get(secondPos);
}
return objSize;
}
@Override
public long getObjectCount() {
- return positions24.size() + positions32.length;
+ return (long) positions24.size() + positions32.size();
}
@Override
@@ -131,69 +133,128 @@ class PackObjectSizeIndexV1 implements PackObjectSizeIndex {
}
/**
- * Wrapper to read parsed content from the byte stream
+ * A byte[] that should be interpreted as an int[]
*/
- private static class IndexInputStreamReader {
+ private static class IntArray {
+ private static final IntArray EMPTY = new IntArray(new byte[0]);
- private final byte[] buffer = new byte[8];
+ private static final int INT_SIZE = 4;
- private final InputStream in;
+ private final byte[] data;
- IndexInputStreamReader(InputStream in) {
- this.in = in;
- }
+ private final int size;
- int readInt() throws IOException {
- int n = in.readNBytes(buffer, 0, 4);
- if (n < 4) {
- throw new IOException(JGitText.get().unableToReadFullInt);
+ static IntArray from(InputStream in, int ints) throws IOException {
+ int expectedBytes = ints * INT_SIZE;
+ byte[] data = in.readNBytes(expectedBytes);
+ if (data.length < expectedBytes) {
+ throw new IOException(MessageFormat
+ .format(JGitText.get().unableToReadFullArray,
+ Integer.valueOf(ints)));
}
- return NB.decodeInt32(buffer, 0);
+ return new IntArray(data);
+ }
+
+ private IntArray(byte[] data) {
+ this.data = data;
+ size = data.length / INT_SIZE;
}
- int[] readIntArray(int intsCount) throws IOException {
- if (intsCount == 0) {
- return new int[0];
+ /**
+ * Returns position of element in array, -1 if not there
+ *
+ * @param needle
+ * element to look for
+ * @return position of the element in the array or -1 if not found
+ */
+ int binarySearch(int needle) {
+ if (size == 0) {
+ return -1;
}
+ int high = size;
+ int low = 0;
+ do {
+ int mid = (low + high) >>> 1;
+ int cmp = Integer.compare(needle, get(mid));
+ if (cmp < 0)
+ high = mid;
+ else if (cmp == 0) {
+ return mid;
+ } else
+ low = mid + 1;
+ } while (low < high);
+ return -1;
+ }
- int[] dest = new int[intsCount];
- for (int i = 0; i < intsCount; i++) {
- dest[i] = readInt();
+ int get(int position) {
+ if (position < 0 || position >= size) {
+ throw new IndexOutOfBoundsException(position);
}
- return dest;
+ return NB.decodeInt32(data, position * INT_SIZE);
}
- long readLong() throws IOException {
- int n = in.readNBytes(buffer, 0, 8);
- if (n < 8) {
- throw new IOException(JGitText.get().unableToReadFullInt);
+ boolean empty() {
+ return size == 0;
+ }
+
+ int size() {
+ return size;
+ }
+ }
+
+ /**
+ * A byte[] that should be interpreted as an long[]
+ */
+ private static class LongArray {
+ private static final LongArray EMPTY = new LongArray(new byte[0]);
+
+ private static final int LONG_SIZE = 8; // bytes
+
+ private final byte[] data;
+
+ private final int size;
+
+ static LongArray from(InputStream in, int longs) throws IOException {
+ byte[] data = in.readNBytes(longs * LONG_SIZE);
+ if (data.length < longs * LONG_SIZE) {
+ throw new IOException(MessageFormat
+ .format(JGitText.get().unableToReadFullArray,
+ Integer.valueOf(longs)));
}
- return NB.decodeInt64(buffer, 0);
+ return new LongArray(data);
}
- long[] readLongArray(int longsCount) throws IOException {
- if (longsCount == 0) {
- return new long[0];
+ private LongArray(byte[] data) {
+ this.data = data;
+ size = data.length / LONG_SIZE;
+ }
+
+ long get(int position) {
+ if (position < 0 || position >= size) {
+ throw new IndexOutOfBoundsException(position);
}
+ return NB.decodeInt64(data, position * LONG_SIZE);
+ }
+ }
- long[] dest = new long[longsCount];
- for (int i = 0; i < longsCount; i++) {
- dest[i] = readLong();
+ private static class StreamHelper {
+ private final byte[] buffer = new byte[8];
+
+ int readInt(InputStream in) throws IOException {
+ int n = in.readNBytes(buffer, 0, 4);
+ if (n < 4) {
+ throw new IOException(JGitText.get().unableToReadFullInt);
}
- return dest;
+ return NB.decodeInt32(buffer, 0);
}
- byte readByte() throws IOException {
+ byte readByte(InputStream in) throws IOException {
int n = in.readNBytes(buffer, 0, 1);
if (n != 1) {
throw new IOException(JGitText.get().cannotReadByte);
}
return buffer[0];
}
-
- byte[] readNBytes(int sz) throws IOException {
- return in.readNBytes(sz);
- }
}
private static class EmptyPackObjectSizeIndex
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexWriter.java
index 65a065dd55..328643688d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackObjectSizeIndexWriter.java
@@ -72,11 +72,13 @@ public abstract class PackObjectSizeIndexWriter {
*
* Store position (in the main index) to size as parallel arrays.
*
- * <p>Positions in the main index fit well in unsigned 24 bits (16M) for most
+ * <p>
+ * Positions in the main index fit well in unsigned 24 bits (16M) for most
* repositories, but some outliers have even more objects, so we need to
* store also 32 bits positions.
*
- * <p>Sizes are stored as a first array parallel to positions. If a size
+ * <p>
+ * Sizes are stored as a first array parallel to positions. If a size
* doesn't fit in an element of that array, then we encode there a position
* on the next-size array. This "overflow" array doesn't have entries for
* all positions.
@@ -85,30 +87,36 @@ public abstract class PackObjectSizeIndexWriter {
*
* positions [10, 500, 1000, 1001]
* sizes (32bits) [15MB, -1, 6MB, -2]
- * ___/ ______/
+ * ___/ ______/
* / /
* sizes (64 bits) [3GB, 6GB]
* </pre>
*
- * <p>For sizes we use 32 bits as the first level and 64 for the rare objects
+ * <p>
+ * For sizes we use 32 bits as the first level and 64 for the rare objects
* over 2GB.
*
- * <p>A 24/32/64 bits hierarchy of arrays saves space if we have a lot of small
- * objects, but wastes space if we have only big ones. The min size to index is
- * controlled by conf and in principle we want to index only rather
- * big objects (e.g. > 10MB). We could support more dynamics read/write of sizes
+ * <p>
+ * A 24/32/64 bits hierarchy of arrays saves space if we have a lot of small
+ * objects, but wastes space if we have only big ones. The min size to index
+ * is controlled by conf and in principle we want to index only rather big
+ * objects (e.g. > 10MB). We could support more dynamics read/write of sizes
* (e.g. 24 only if the threshold will include many of those objects) but it
- * complicates a lot code and spec. If needed it could go for a v2 of the protocol.
- *
- * <p>Format:
+ * complicates a lot code and spec. If needed it could go for a v2 of the
+ * protocol.
*
+ * <p>
+ * Format:
+ * <ul>
* <li>A header with the magic number (4 bytes)
* <li>The index version (1 byte)
* <li>The minimum object size (4 bytes)
* <li>Total count of objects indexed (C, 4 bytes)
+ * </ul>
* (if count == 0, stop here)
- *
+ * <p>
* Blocks of
+ * <ul>
* <li>Size per entry in bits (1 byte, either 24 (0x18) or 32 (0x20))
* <li>Count of entries (4 bytes) (c, as a signed int)
* <li>positions encoded in s bytes each (i.e s*c bytes)
@@ -120,6 +128,7 @@ public abstract class PackObjectSizeIndexWriter {
* <li>Count of 64 bit sizes (s64) (or 0 if no more indirections)
* <li>64 bit sizes (s64 * 8 bytes)
* <li>0 (end)
+ * </ul>
*/
static class PackObjectSizeWriterV1 extends PackObjectSizeIndexWriter {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java
index 1a5adb4a16..720a3bcbff 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndex.java
@@ -10,11 +10,8 @@
package org.eclipse.jgit.internal.storage.file;
-import java.text.MessageFormat;
-
import org.eclipse.jgit.errors.CorruptObjectException;
-import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
+import org.eclipse.jgit.errors.PackMismatchException;
import org.eclipse.jgit.lib.ObjectId;
/**
@@ -27,92 +24,27 @@ import org.eclipse.jgit.lib.ObjectId;
* @see PackIndex
* @see Pack
*/
-public class PackReverseIndex {
- /** Index we were created from, and that has our ObjectId data. */
- private final PackIndex index;
-
- /** The number of bytes per entry in the offsetIndex. */
- private final long bucketSize;
-
+public interface PackReverseIndex {
/**
- * An index into the nth mapping, where the value is the position after the
- * the last index that contains the values of the bucket. For example given
- * offset o (and bucket = o / bucketSize), the offset will be contained in
- * the range nth[offsetIndex[bucket - 1]] inclusive to
- * nth[offsetIndex[bucket]] exclusive.
- *
- * See {@link #binarySearch}
+ * Magic bytes that uniquely identify git reverse index files.
*/
- private final int[] offsetIndex;
+ byte[] MAGIC = { 'R', 'I', 'D', 'X' };
- /** Mapping from indices in offset order to indices in SHA-1 order. */
- private final int[] nth;
+ /**
+ * The first reverse index file version.
+ */
+ int VERSION_1 = 1;
/**
- * Create reverse index from straight/forward pack index, by indexing all
- * its entries.
+ * Verify that the pack checksum found in the reverse index matches that
+ * from the pack file.
*
- * @param packIndex
- * forward index - entries to (reverse) index.
+ * @param packFilePath
+ * the path to display in event of a mismatch
+ * @throws PackMismatchException
+ * if the checksums do not match
*/
- public PackReverseIndex(PackIndex packIndex) {
- index = packIndex;
-
- final long cnt = index.getObjectCount();
- if (cnt + 1 > Integer.MAX_VALUE)
- throw new IllegalArgumentException(
- JGitText.get().hugeIndexesAreNotSupportedByJgitYet);
-
- if (cnt == 0) {
- bucketSize = Long.MAX_VALUE;
- offsetIndex = new int[1];
- nth = new int[0];
- return;
- }
-
- final long[] offsetsBySha1 = new long[(int) cnt];
-
- long maxOffset = 0;
- int ith = 0;
- for (MutableEntry me : index) {
- final long o = me.getOffset();
- offsetsBySha1[ith++] = o;
- if (o > maxOffset)
- maxOffset = o;
- }
-
- bucketSize = maxOffset / cnt + 1;
- int[] bucketIndex = new int[(int) cnt];
- int[] bucketValues = new int[(int) cnt + 1];
- for (int oi = 0; oi < offsetsBySha1.length; oi++) {
- final long o = offsetsBySha1[oi];
- final int bucket = (int) (o / bucketSize);
- final int bucketValuesPos = oi + 1;
- final int current = bucketIndex[bucket];
- bucketIndex[bucket] = bucketValuesPos;
- bucketValues[bucketValuesPos] = current;
- }
-
- int nthByOffset = 0;
- nth = new int[offsetsBySha1.length];
- offsetIndex = bucketIndex; // Reuse the allocation
- for (int bi = 0; bi < bucketIndex.length; bi++) {
- final int start = nthByOffset;
- // Insertion sort of the values in the bucket.
- for (int vi = bucketIndex[bi]; vi > 0; vi = bucketValues[vi]) {
- final int nthBySha1 = vi - 1;
- final long o = offsetsBySha1[nthBySha1];
- int insertion = nthByOffset++;
- for (; start < insertion; insertion--) {
- if (o > offsetsBySha1[nth[insertion - 1]])
- break;
- nth[insertion] = nth[insertion - 1];
- }
- nth[insertion] = nthBySha1;
- }
- offsetIndex[bi] = nthByOffset;
- }
- }
+ void verifyPackChecksum(String packFilePath) throws PackMismatchException;
/**
* Search for object id with the specified start offset in this pack
@@ -122,12 +54,7 @@ public class PackReverseIndex {
* start offset of object to find.
* @return object id for this offset, or null if no object was found.
*/
- public ObjectId findObject(long offset) {
- final int ith = binarySearch(offset);
- if (ith < 0)
- return null;
- return index.getObjectId(nth[ith]);
- }
+ ObjectId findObject(long offset);
/**
* Search for the next offset to the specified offset in this pack (reverse)
@@ -144,42 +71,25 @@ public class PackReverseIndex {
* @throws org.eclipse.jgit.errors.CorruptObjectException
* when there is no object with the provided offset.
*/
- public long findNextOffset(long offset, long maxOffset)
- throws CorruptObjectException {
- final int ith = binarySearch(offset);
- if (ith < 0)
- throw new CorruptObjectException(
- MessageFormat.format(
- JGitText.get().cantFindObjectInReversePackIndexForTheSpecifiedOffset,
- Long.valueOf(offset)));
-
- if (ith + 1 == nth.length)
- return maxOffset;
- return index.getOffset(nth[ith + 1]);
- }
-
- int findPosition(long offset) {
- return binarySearch(offset);
- }
+ long findNextOffset(long offset, long maxOffset)
+ throws CorruptObjectException;
- private int binarySearch(long offset) {
- int bucket = (int) (offset / bucketSize);
- int low = bucket == 0 ? 0 : offsetIndex[bucket - 1];
- int high = offsetIndex[bucket];
- while (low < high) {
- final int mid = (low + high) >>> 1;
- final long o = index.getOffset(nth[mid]);
- if (offset < o)
- high = mid;
- else if (offset == o)
- return mid;
- else
- low = mid + 1;
- }
- return -1;
- }
+ /**
+ * Find the position in the reverse index of the object at the given pack
+ * offset.
+ *
+ * @param offset
+ * the pack offset of the object
+ * @return the position in the reverse index of the object
+ */
+ int findPosition(long offset);
- ObjectId findObjectByPosition(int nthPosition) {
- return index.getObjectId(nth[nthPosition]);
- }
+ /**
+ * Find the object that is in the given position in the reverse index.
+ *
+ * @param nthPosition
+ * the position of the object in the reverse index
+ * @return the object in that position
+ */
+ ObjectId findObjectByPosition(int nthPosition);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexComputed.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexComputed.java
new file mode 100644
index 0000000000..0b487a2819
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexComputed.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2023, Google LLC and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.file;
+
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.PackMismatchException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * Reverse index for forward pack index which is computed from the forward pack
+ * index.
+ * <p>
+ * Creating an instance uses an insertion sort of the entries in the forward
+ * index, so it runs in quadratic time on average.
+ */
+final class PackReverseIndexComputed implements PackReverseIndex {
+ /**
+ * Index we were created from, and that has our ObjectId data.
+ */
+ private final PackIndex index;
+
+ /**
+ * The difference in offset between the start of an offset bucket and the
+ * start of its succeeding bucket.
+ */
+ private final long bucketSize;
+
+ /**
+ * The indexes into indexPosInOffsetOrder at which the next bucket starts.
+ * <p>
+ * For example, given offset o (and therefore bucket = o / bucketSize), the
+ * indexPos corresponding to o will be contained in the range
+ * indexPosInOffsetOrder[nextBucketStart[bucket - 1]] inclusive to
+ * indexPosInOffsetOrder[nextBucketStart[bucket]] exclusive.
+ * <p>
+ * This range information can speed up #binarySearch by identifying the
+ * relevant bucket and only searching within its range.
+ * <p>
+ * See {@link #binarySearch}
+ */
+ private final int[] nextBucketStart;
+
+ /**
+ * Mapping from indices in offset order to indices in SHA-1 order.
+ */
+ private final int[] indexPosInOffsetOrder;
+
+ /**
+ * Create reverse index from straight/forward pack index, by indexing all
+ * its entries.
+ *
+ * @param packIndex
+ * forward index - entries to (reverse) index.
+ */
+ PackReverseIndexComputed(PackIndex packIndex) {
+ index = packIndex;
+
+ long rawCnt = index.getObjectCount();
+ if (rawCnt + 1 > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException(
+ JGitText.get().hugeIndexesAreNotSupportedByJgitYet);
+ }
+ int cnt = (int) rawCnt;
+
+ if (cnt == 0) {
+ bucketSize = Long.MAX_VALUE;
+ nextBucketStart = new int[1];
+ indexPosInOffsetOrder = new int[0];
+ return;
+ }
+
+ // Sort the index positions according to the corresponding pack offsets.
+ // Use bucket sort since the offsets are somewhat uniformly distributed
+ // over the range (0, pack size).
+ long[] offsetsInIndexOrder = new long[cnt];
+ long maxOffset = 0;
+ int i = 0;
+ for (MutableEntry entry : index) {
+ long offset = entry.getOffset();
+ offsetsInIndexOrder[i++] = offset;
+ if (offset > maxOffset) {
+ maxOffset = offset;
+ }
+ }
+
+ bucketSize = maxOffset / cnt + 1;
+ // The values in each bucket, stored as a linked list. Given a bucket,
+ // headValues[bucket] contains the first value,
+ // furtherValues[headValues[bucket]] contains the second,
+ // furtherValues[furtherValues[headValues[bucket]]] the third, and so
+ // on. The linked list stops when a value is 0. The values themselves
+ // are shifted index positions. There won't be any
+ // collisions because every index position is unique.
+ int[] headValues = new int[cnt];
+ int[] furtherValues = new int[cnt + 1];
+ for (int indexPos = 0; indexPos < cnt; indexPos++) {
+ // The offset determines which bucket this index position falls
+ // into, since the goal is sort into offset order.
+ long offset = offsetsInIndexOrder[indexPos];
+ int bucket = (int) (offset / bucketSize);
+ // Store the index positions as 1-indexed so that default
+ // initialized value 0 can be interpreted as the end of the bucket
+ // values.
+ int asBucketValue = indexPos + 1;
+ // If there is an existing value in this bucket, push the value to
+ // the front of the linked list.
+ int current = headValues[bucket];
+ headValues[bucket] = asBucketValue;
+ furtherValues[asBucketValue] = current;
+ }
+
+ int nthByOffset = 0;
+ indexPosInOffsetOrder = new int[cnt];
+ nextBucketStart = headValues; // Reuse the allocation
+ for (int bi = 0; bi < headValues.length; bi++) {
+ // Insertion sort of the values in the bucket.
+ int start = nthByOffset;
+ for (int vi = headValues[bi]; vi > 0; vi = furtherValues[vi]) {
+ int nthBySha1 = vi - 1;
+ long o = offsetsInIndexOrder[nthBySha1];
+ int insertion = nthByOffset++;
+ for (; start < insertion; insertion--) {
+ if (o > offsetsInIndexOrder[indexPosInOffsetOrder[insertion
+ - 1]]) {
+ break;
+ }
+ indexPosInOffsetOrder[insertion] = indexPosInOffsetOrder[insertion
+ - 1];
+ }
+ indexPosInOffsetOrder[insertion] = nthBySha1;
+ }
+ nextBucketStart[bi] = nthByOffset;
+ }
+ }
+
+ @Override
+ public void verifyPackChecksum(String packFilePath)
+ throws PackMismatchException {
+ // There is no file with a checksum.
+ }
+
+ @Override
+ public ObjectId findObject(long offset) {
+ final int ith = binarySearch(offset);
+ if (ith < 0) {
+ return null;
+ }
+ return index.getObjectId(indexPosInOffsetOrder[ith]);
+ }
+
+ @Override
+ public long findNextOffset(long offset, long maxOffset)
+ throws CorruptObjectException {
+ final int ith = binarySearch(offset);
+ if (ith < 0) {
+ throw new CorruptObjectException(MessageFormat.format(JGitText
+ .get().cantFindObjectInReversePackIndexForTheSpecifiedOffset,
+ Long.valueOf(offset)));
+ }
+
+ if (ith + 1 == indexPosInOffsetOrder.length) {
+ return maxOffset;
+ }
+ return index.getOffset(indexPosInOffsetOrder[ith + 1]);
+ }
+
+ @Override
+ public int findPosition(long offset) {
+ return binarySearch(offset);
+ }
+
+ private int binarySearch(long offset) {
+ int bucket = (int) (offset / bucketSize);
+ int low = bucket == 0 ? 0 : nextBucketStart[bucket - 1];
+ int high = nextBucketStart[bucket];
+ while (low < high) {
+ final int mid = (low + high) >>> 1;
+ final long o = index.getOffset(indexPosInOffsetOrder[mid]);
+ if (offset < o) {
+ high = mid;
+ } else if (offset == o) {
+ return mid;
+ } else {
+ low = mid + 1;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public ObjectId findObjectByPosition(int nthPosition) {
+ return index.getObjectId(indexPosInOffsetOrder[nthPosition]);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexFactory.java
new file mode 100644
index 0000000000..32830c3cf0
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexFactory.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023, Google LLC and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.file;
+
+import static org.eclipse.jgit.internal.storage.file.PackReverseIndex.MAGIC;
+import static org.eclipse.jgit.internal.storage.file.PackReverseIndex.VERSION_1;
+
+import java.io.DataInput;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.DigestInputStream;
+import java.text.MessageFormat;
+import java.util.Arrays;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.io.SilentFileInputStream;
+
+/**
+ * Factory for creating instances of {@link PackReverseIndex}.
+ */
+public final class PackReverseIndexFactory {
+ /**
+ * Create an in-memory pack reverse index by reading it from the given file
+ * if the file exists, or computing it from the given pack index if the file
+ * doesn't exist.
+ *
+ * @param idxFile
+ * the file to read the pack file from, if it exists
+ * @param objectCount
+ * the number of objects in the corresponding pack
+ * @param packIndexSupplier
+ * a function to lazily get the corresponding forward index
+ * @return the reverse index instance
+ * @throws IOException
+ * if reading from the file fails
+ */
+ static PackReverseIndex openOrCompute(File idxFile, long objectCount,
+ PackBitmapIndex.SupplierWithIOException<PackIndex> packIndexSupplier)
+ throws IOException {
+ try (SilentFileInputStream fd = new SilentFileInputStream(idxFile)) {
+ return readFromFile(fd, objectCount, packIndexSupplier);
+ } catch (FileNotFoundException e) {
+ return computeFromIndex(packIndexSupplier.get());
+ } catch (IOException e) {
+ throw new IOException(
+ MessageFormat.format(JGitText.get().unreadablePackIndex,
+ idxFile.getAbsolutePath()),
+ e);
+ }
+ }
+
+ /**
+ * Compute an in-memory pack reverse index from the in-memory pack forward
+ * index. This computation uses insertion sort, which has a quadratic
+ * runtime on average.
+ *
+ * @param packIndex
+ * the forward index to compute from
+ * @return the reverse index instance
+ */
+ public static PackReverseIndex computeFromIndex(PackIndex packIndex) {
+ return new PackReverseIndexComputed(packIndex);
+ }
+
+ /**
+ * Read an in-memory pack reverse index from the given input stream. This
+ * has a linear runtime.
+ *
+ * @param src
+ * the input stream to read the contents from
+ * @param objectCount
+ * the number of objects in the corresponding pack
+ * @param packIndexSupplier
+ * a function to lazily get the corresponding forward index
+ * @return the reverse index instance
+ * @throws IOException
+ * if reading from the input stream fails
+ */
+ static PackReverseIndex readFromFile(InputStream src, long objectCount,
+ PackBitmapIndex.SupplierWithIOException<PackIndex> packIndexSupplier)
+ throws IOException {
+ final DigestInputStream digestIn = new DigestInputStream(src,
+ Constants.newMessageDigest());
+
+ final byte[] magic = new byte[MAGIC.length];
+ IO.readFully(digestIn, magic);
+ if (!Arrays.equals(magic, MAGIC)) {
+ throw new IOException(
+ MessageFormat.format(JGitText.get().expectedGot,
+ Arrays.toString(MAGIC), Arrays.toString(magic)));
+ }
+
+ DataInput dataIn = new SimpleDataInput(digestIn);
+ int version = dataIn.readInt();
+ switch (version) {
+ case VERSION_1:
+ return new PackReverseIndexV1(digestIn, objectCount,
+ packIndexSupplier);
+ default:
+ throw new IOException(MessageFormat.format(
+ JGitText.get().unsupportedPackReverseIndexVersion,
+ String.valueOf(version)));
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexV1.java
new file mode 100644
index 0000000000..6a47352124
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexV1.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2022, Google LLC and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.file;
+
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+
+import java.io.DataInput;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.security.DigestInputStream;
+import java.text.MessageFormat;
+import java.util.Arrays;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.PackMismatchException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.util.Hex;
+import org.eclipse.jgit.util.IO;
+
+/**
+ * Reverse index for forward pack index which is parsed from a version 1 file.
+ * <p>
+ * The file format is specified at
+ * https://git-scm.com/docs/pack-format#_pack_rev_files_have_the_format.
+ */
+final class PackReverseIndexV1 implements PackReverseIndex {
+ static final int OID_VERSION_SHA1 = 1;
+
+ static final int OID_VERSION_SHA256 = 2;
+
+ private static final int SHA1_BYTES = OBJECT_ID_LENGTH;
+
+ private final DigestInputStream inputStream;
+
+ private final DataInput dataIn;
+
+ /**
+ * A lazy supplier for the corresponding PackIndex. The PackIndex is not
+ * needed during instantiation and parsing, only later when querying the
+ * reverse index. Allow lazy loading so that the parsing of the forward and
+ * reverse indices could happen in parallel.
+ */
+ private final PackBitmapIndex.SupplierWithIOException<PackIndex> packIndexSupplier;
+
+ private int objectCount;
+
+ private byte[] packChecksum;
+
+ private int[] indexPositionsSortedByOffset;
+
+ private PackIndex packIndex;
+
+ PackReverseIndexV1(DigestInputStream inputStream, long objectCount,
+ PackBitmapIndex.SupplierWithIOException<PackIndex> packIndexSupplier)
+ throws IOException {
+ try {
+ this.objectCount = Math.toIntExact(objectCount);
+ } catch (ArithmeticException e) {
+ throw new IllegalArgumentException(
+ JGitText.get().hugeIndexesAreNotSupportedByJgitYet, e);
+ }
+
+ this.inputStream = inputStream;
+ dataIn = new SimpleDataInput(inputStream);
+
+ int oid_version = dataIn.readInt();
+ switch (oid_version) {
+ case OID_VERSION_SHA1:
+ // JGit Pack only supports AnyObjectId, which represents SHA1.
+ break;
+ case OID_VERSION_SHA256:
+ throw new IOException(MessageFormat.format(
+ JGitText.get().unsupportedObjectIdVersion, "SHA256")); //$NON-NLS-1$
+ default:
+ throw new IOException(MessageFormat.format(
+ JGitText.get().unsupportedObjectIdVersion,
+ String.valueOf(oid_version)));
+ }
+
+ indexPositionsSortedByOffset = new int[this.objectCount];
+ this.packIndexSupplier = packIndexSupplier;
+
+ parseBody();
+ parseChecksums();
+ }
+
+ @Override
+ public void verifyPackChecksum(String packFilePath)
+ throws PackMismatchException {
+ if (!Arrays.equals(packChecksum, getPackIndex().getChecksum())) {
+ throw new PackMismatchException(
+ MessageFormat.format(JGitText.get().packChecksumMismatch,
+ packFilePath, PackExt.INDEX.getExtension(),
+ Hex.toHexString(getPackIndex().getChecksum()),
+ PackExt.REVERSE_INDEX.getExtension(),
+ Hex.toHexString(packChecksum)));
+ }
+ }
+
+ private void parseBody() throws IOException {
+ for (int i = 0; i < objectCount; i++) {
+ indexPositionsSortedByOffset[i] = dataIn.readInt();
+ }
+ }
+
+ private void parseChecksums() throws IOException {
+ packChecksum = new byte[SHA1_BYTES];
+ IO.readFully(inputStream, packChecksum);
+
+ // Take digest before reading the self checksum changes it.
+ byte[] observedSelfChecksum = inputStream.getMessageDigest().digest();
+
+ byte[] readSelfChecksum = new byte[SHA1_BYTES];
+ IO.readFully(inputStream, readSelfChecksum);
+
+ if (!Arrays.equals(readSelfChecksum, observedSelfChecksum)) {
+ throw new CorruptObjectException(MessageFormat.format(
+ JGitText.get().corruptReverseIndexChecksumIncorrect,
+ Hex.toHexString(readSelfChecksum),
+ Hex.toHexString(observedSelfChecksum)));
+ }
+ }
+
+ @Override
+ public ObjectId findObject(long offset) {
+ int reversePosition = findPosition(offset);
+ if (reversePosition < 0) {
+ return null;
+ }
+ int forwardPosition = findForwardPositionByReversePosition(
+ reversePosition);
+ return getPackIndex().getObjectId(forwardPosition);
+ }
+
+ @Override
+ public long findNextOffset(long offset, long maxOffset)
+ throws CorruptObjectException {
+ int position = findPosition(offset);
+ if (position < 0) {
+ throw new CorruptObjectException(MessageFormat.format(JGitText
+ .get().cantFindObjectInReversePackIndexForTheSpecifiedOffset,
+ Long.valueOf(offset)));
+ }
+ if (position + 1 == objectCount) {
+ return maxOffset;
+ }
+ return findOffsetByReversePosition(position + 1);
+ }
+
+ @Override
+ public int findPosition(long offset) {
+ return binarySearchByOffset(offset);
+ }
+
+ @Override
+ public ObjectId findObjectByPosition(int position) {
+ return getPackIndex()
+ .getObjectId(findForwardPositionByReversePosition(position));
+ }
+
+ private long findOffsetByReversePosition(int position) {
+ return getPackIndex()
+ .getOffset(findForwardPositionByReversePosition(position));
+ }
+
+ private int findForwardPositionByReversePosition(int reversePosition) {
+ assert (reversePosition >= 0);
+ assert (reversePosition < indexPositionsSortedByOffset.length);
+ return indexPositionsSortedByOffset[reversePosition];
+ }
+
+ private int binarySearchByOffset(long wantedOffset) {
+ int low = 0;
+ int high = objectCount - 1;
+ while (low <= high) {
+ int mid = (low + high) >>> 1;
+ long offsetAtMid = findOffsetByReversePosition(mid);
+ if (offsetAtMid == wantedOffset) {
+ return mid;
+ } else if (offsetAtMid > wantedOffset) {
+ high = mid - 1;
+ } else {
+ low = mid + 1;
+ }
+ }
+ return -1;
+ }
+
+ private PackIndex getPackIndex() {
+ if (packIndex == null) {
+ try {
+ packIndex = packIndexSupplier.get();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ return packIndex;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexWriter.java
index 4c8417b115..94a4e9c129 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexWriter.java
@@ -9,6 +9,8 @@
*/
package org.eclipse.jgit.internal.storage.file;
+import static org.eclipse.jgit.internal.storage.file.PackReverseIndex.VERSION_1;
+
import java.io.BufferedOutputStream;
import java.io.DataOutput;
import java.io.IOException;
@@ -29,16 +31,6 @@ import org.eclipse.jgit.transport.PackedObjectInfo;
*/
public abstract class PackReverseIndexWriter {
/**
- * Magic bytes that uniquely identify git reverse index files.
- */
- protected static byte[] MAGIC = { 'R', 'I', 'D', 'X' };
-
- /**
- * The first reverse index file version.
- */
- protected static final int VERSION_1 = 1;
-
- /**
* Stream to write contents to while maintaining a checksum.
*/
protected final DigestOutputStream out;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexWriterV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexWriterV1.java
index 7630724d09..ff1809f446 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexWriterV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackReverseIndexWriterV1.java
@@ -9,6 +9,10 @@
*/
package org.eclipse.jgit.internal.storage.file;
+import static org.eclipse.jgit.internal.storage.file.PackReverseIndex.MAGIC;
+import static org.eclipse.jgit.internal.storage.file.PackReverseIndex.VERSION_1;
+import static org.eclipse.jgit.internal.storage.file.PackReverseIndexV1.OID_VERSION_SHA1;
+
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
@@ -24,8 +28,6 @@ import org.eclipse.jgit.util.IntList.IntComparator;
* https://git-scm.com/docs/pack-format#_pack_rev_files_have_the_format.
*/
final class PackReverseIndexWriterV1 extends PackReverseIndexWriter {
- private static final int OID_VERSION_SHA1 = 1;
-
private static final int DEFAULT_OID_VERSION = OID_VERSION_SHA1;
PackReverseIndexWriterV1(final OutputStream dst) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
index 106313db63..5584f13db1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java
@@ -98,7 +98,6 @@ class PackedBatchRefUpdate extends BatchRefUpdate {
this.shouldLockLooseRefs = shouldLockLooseRefs;
}
- /** {@inheritDoc} */
@Override
public void execute(RevWalk walk, ProgressMonitor monitor,
List<String> options) throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
index 0416a648e5..319a9ed710 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java
@@ -16,6 +16,7 @@ package org.eclipse.jgit.internal.storage.file;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.lib.Constants.LOGS;
+import static org.eclipse.jgit.lib.Constants.L_LOGS;
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
import static org.eclipse.jgit.lib.Constants.PACKED_REFS;
import static org.eclipse.jgit.lib.Constants.R_HEADS;
@@ -37,39 +38,44 @@ import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.text.MessageFormat;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.api.PackRefsCommand;
import org.eclipse.jgit.errors.InvalidObjectIdException;
import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.ObjectWritingException;
import org.eclipse.jgit.events.RefsChangedEvent;
import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.CoreConfig.TrustPackedRefsStat;
+import org.eclipse.jgit.lib.CoreConfig;
+import org.eclipse.jgit.lib.CoreConfig.TrustStat;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefComparator;
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefWriter;
+import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.SymbolicRef;
import org.eclipse.jgit.revwalk.RevObject;
@@ -81,6 +87,7 @@ import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.RefList;
import org.eclipse.jgit.util.RefMap;
+import org.eclipse.jgit.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -120,6 +127,8 @@ public class RefDirectory extends RefDatabase {
private final File gitDir;
+ private final File gitCommonDir;
+
final File refsDir;
final File packedRefsFile;
@@ -155,7 +164,7 @@ public class RefDirectory extends RefDatabase {
* {@code RepositoryCache} is used, this lock instance will be used by all
* threads.
*/
- final ReentrantLock inProcessPackedRefsLock = new ReentrantLock(true);
+ final ReentrantLock inProcessPackedRefsLock;
/**
* Number of modifications made to this database.
@@ -175,41 +184,36 @@ public class RefDirectory extends RefDatabase {
private List<Integer> retrySleepMs = RETRY_SLEEP_MS;
- private final boolean trustFolderStat;
-
- private final TrustPackedRefsStat trustPackedRefsStat;
+ private final CoreConfig coreConfig;
RefDirectory(RefDirectory refDb) {
parent = refDb.parent;
gitDir = refDb.gitDir;
+ gitCommonDir = refDb.gitCommonDir;
refsDir = refDb.refsDir;
logsDir = refDb.logsDir;
logsRefsDir = refDb.logsRefsDir;
packedRefsFile = refDb.packedRefsFile;
looseRefs.set(refDb.looseRefs.get());
packedRefs.set(refDb.packedRefs.get());
- trustFolderStat = refDb.trustFolderStat;
- trustPackedRefsStat = refDb.trustPackedRefsStat;
+ coreConfig = refDb.coreConfig;
+ inProcessPackedRefsLock = refDb.inProcessPackedRefsLock;
}
RefDirectory(FileRepository db) {
final FS fs = db.getFS();
parent = db;
gitDir = db.getDirectory();
- refsDir = fs.resolve(gitDir, R_REFS);
- logsDir = fs.resolve(gitDir, LOGS);
- logsRefsDir = fs.resolve(gitDir, LOGS + '/' + R_REFS);
- packedRefsFile = fs.resolve(gitDir, PACKED_REFS);
+ gitCommonDir = db.getCommonDirectory();
+ refsDir = fs.resolve(gitCommonDir, R_REFS);
+ logsDir = fs.resolve(gitCommonDir, LOGS);
+ logsRefsDir = fs.resolve(gitCommonDir, L_LOGS + R_REFS);
+ packedRefsFile = fs.resolve(gitCommonDir, PACKED_REFS);
looseRefs.set(RefList.<LooseRef> emptyList());
packedRefs.set(NO_PACKED_REFS);
- trustFolderStat = db.getConfig()
- .getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
- ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
- trustPackedRefsStat = db.getConfig()
- .getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
- ConfigConstants.CONFIG_KEY_TRUST_PACKED_REFS_STAT,
- TrustPackedRefsStat.UNSET);
+ coreConfig = db.getConfig().get(CoreConfig.KEY);
+ inProcessPackedRefsLock = new ReentrantLock(true);
}
Repository getRepository() {
@@ -245,7 +249,6 @@ public class RefDirectory extends RefDatabase {
return new SnapshottingRefDirectory(this);
}
- /** {@inheritDoc} */
@Override
public void create() throws IOException {
FileUtils.mkdir(refsDir);
@@ -254,7 +257,6 @@ public class RefDirectory extends RefDatabase {
newLogWriter(false).create();
}
- /** {@inheritDoc} */
@Override
public void close() {
clearReferences();
@@ -265,14 +267,39 @@ public class RefDirectory extends RefDatabase {
packedRefs.set(NO_PACKED_REFS);
}
- /** {@inheritDoc} */
@Override
public void refresh() {
super.refresh();
clearReferences();
}
- /** {@inheritDoc} */
+ /**
+ * {@inheritDoc}
+ *
+ * For a RefDirectory database, by default this packs non-symbolic, loose
+ * tag refs into packed-refs. If {@code all} flag is set, this packs all the
+ * non-symbolic, loose refs.
+ */
+ @Override
+ public void packRefs(ProgressMonitor pm, PackRefsCommand packRefs)
+ throws IOException {
+ String prefix = packRefs.isAll() ? R_REFS : R_TAGS;
+ Collection<Ref> refs = getRefsByPrefix(prefix);
+ List<String> refsToBePacked = new ArrayList<>(refs.size());
+ pm.beginTask(JGitText.get().packRefs, refs.size());
+ try {
+ for (Ref ref : refs) {
+ if (!ref.isSymbolic() && ref.getStorage().isLoose()) {
+ refsToBePacked.add(ref.getName());
+ }
+ pm.update(1);
+ }
+ pack(refsToBePacked);
+ } finally {
+ pm.endTask();
+ }
+ }
+
@Override
public boolean isNameConflicting(String name) throws IOException {
// Cannot be nested within an existing reference.
@@ -310,7 +337,6 @@ public class RefDirectory extends RefDatabase {
}
}
- /** {@inheritDoc} */
@Override
public Ref exactRef(String name) throws IOException {
try {
@@ -320,7 +346,6 @@ public class RefDirectory extends RefDatabase {
}
}
- /** {@inheritDoc} */
@Override
@NonNull
public Map<String, Ref> exactRef(String... refs) throws IOException {
@@ -339,7 +364,6 @@ public class RefDirectory extends RefDatabase {
}
}
- /** {@inheritDoc} */
@Override
@Nullable
public Ref firstExactRef(String... refs) throws IOException {
@@ -357,7 +381,6 @@ public class RefDirectory extends RefDatabase {
}
}
- /** {@inheritDoc} */
@Override
public Map<String, Ref> getRefs(String prefix) throws IOException {
final RefList<LooseRef> oldLoose = looseRefs.get();
@@ -397,10 +420,18 @@ public class RefDirectory extends RefDatabase {
return new RefMap(prefix, packed, upcast(loose), symbolic.toRefList());
}
- /** {@inheritDoc} */
+ @Override
+ public List<Ref> getRefsByPrefix(String... prefixes) throws IOException {
+ return getRefsByPrefix(StringUtils.commonPrefix(prefixes))
+ .parallelStream()
+ .filter(ref -> Stream.of(prefixes)
+ .anyMatch(ref.getName()::startsWith))
+ .collect(Collectors.toUnmodifiableList());
+ }
+
@Override
public List<Ref> getAdditionalRefs() throws IOException {
- List<Ref> ret = new LinkedList<>();
+ List<Ref> ret = new ArrayList<>();
for (String name : additionalRefsNames) {
Ref r = exactRef(name);
if (r != null)
@@ -409,6 +440,11 @@ public class RefDirectory extends RefDatabase {
return ret;
}
+ @Override
+ public ReflogReader getReflogReader(Ref ref) throws IOException {
+ return new ReflogReaderImpl(getRepository(), ref.getName());
+ }
+
@SuppressWarnings("unchecked")
private RefList<Ref> upcast(RefList<? extends Ref> loose) {
return (RefList<Ref>) loose;
@@ -532,7 +568,6 @@ public class RefDirectory extends RefDatabase {
}
}
- /** {@inheritDoc} */
@Override
public Ref peel(Ref ref) throws IOException {
final Ref leaf = ref.getLeaf();
@@ -583,7 +618,6 @@ public class RefDirectory extends RefDatabase {
fireRefsChanged();
}
- /** {@inheritDoc} */
@Override
public RefDirectoryUpdate newUpdate(String name, boolean detach)
throws IOException {
@@ -607,7 +641,6 @@ public class RefDirectory extends RefDatabase {
return new RefDirectoryUpdate(this, ref);
}
- /** {@inheritDoc} */
@Override
public RefDirectoryRename newRename(String fromName, String toName)
throws IOException {
@@ -620,7 +653,6 @@ public class RefDirectory extends RefDatabase {
return new RefDirectoryRename(from, to);
}
- /** {@inheritDoc} */
@Override
public PackedBatchRefUpdate newBatchUpdate() {
return new PackedBatchRefUpdate(this);
@@ -641,7 +673,6 @@ public class RefDirectory extends RefDatabase {
return new PackedBatchRefUpdate(this, shouldLockLooseRefs);
}
- /** {@inheritDoc} */
@Override
public boolean performsAtomicTransactions() {
return true;
@@ -670,42 +701,47 @@ public class RefDirectory extends RefDatabase {
}
String name = dst.getName();
- // Write the packed-refs file using an atomic update. We might
- // wind up reading it twice, before and after the lock, to ensure
- // we don't miss an edit made externally.
- PackedRefList packed = getPackedRefs();
- if (packed.contains(name)) {
- inProcessPackedRefsLock.lock();
+ // Get and keep the packed-refs lock while updating packed-refs and
+ // removing any loose ref
+ inProcessPackedRefsLock.lock();
+ try {
+ LockFile lck = lockPackedRefsOrThrow();
try {
- LockFile lck = lockPackedRefsOrThrow();
- try {
+ // Write the packed-refs file using an atomic update. We might
+ // wind up reading it twice, before and after checking if the
+ // ref to delete is included or not, to ensure
+ // we don't rely on a PackedRefList that is a result of in-memory
+ // or NFS caching.
+ PackedRefList packed = getPackedRefs();
+ if (packed.contains(name)) {
+ // Force update our packed-refs snapshot before writing
packed = refreshPackedRefs();
int idx = packed.find(name);
if (0 <= idx) {
commitPackedRefs(lck, packed.remove(idx), packed, true);
}
- } finally {
- lck.unlock();
}
- } finally {
- inProcessPackedRefsLock.unlock();
- }
- }
- RefList<LooseRef> curLoose, newLoose;
- do {
- curLoose = looseRefs.get();
- int idx = curLoose.find(name);
- if (idx < 0)
- break;
- newLoose = curLoose.remove(idx);
- } while (!looseRefs.compareAndSet(curLoose, newLoose));
+ RefList<LooseRef> curLoose, newLoose;
+ do {
+ curLoose = looseRefs.get();
+ int idx = curLoose.find(name);
+ if (idx < 0) {
+ break;
+ }
+ newLoose = curLoose.remove(idx);
+ } while (!looseRefs.compareAndSet(curLoose, newLoose));
- int levels = levelsIn(name) - 2;
- delete(logFor(name), levels);
- if (dst.getStorage().isLoose()) {
- update.unlock();
- delete(fileFor(name), levels);
+ int levels = levelsIn(name) - 2;
+ delete(logFor(name), levels);
+ if (dst.getStorage().isLoose()) {
+ deleteAndUnlock(fileFor(name), levels, update);
+ }
+ } finally {
+ lck.unlock();
+ }
+ } finally {
+ inProcessPackedRefsLock.unlock();
}
modCnt.incrementAndGet();
@@ -722,6 +758,7 @@ public class RefDirectory extends RefDatabase {
* @param refs
* the refs to be added. Must be fully qualified.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void pack(List<String> refs) throws IOException {
pack(refs, Collections.emptyMap());
@@ -811,7 +848,7 @@ public class RefDirectory extends RefDatabase {
}
Ref packedRef = newPacked.get(refName);
ObjectId clr_oid = currentLooseRef.getObjectId();
- if (clr_oid != null
+ if (clr_oid != null && packedRef != null
&& clr_oid.equals(packedRef.getObjectId())) {
RefList<LooseRef> curLoose, newLoose;
do {
@@ -823,7 +860,7 @@ public class RefDirectory extends RefDatabase {
newLoose = curLoose.remove(idx);
} while (!looseRefs.compareAndSet(curLoose, newLoose));
int levels = levelsIn(refName) - 2;
- delete(refFile, levels, rLck);
+ deleteAndUnlock(refFile, levels, rLck);
}
} finally {
if (shouldUnlock) {
@@ -866,26 +903,29 @@ public class RefDirectory extends RefDatabase {
* has this attributes simply return it. Otherwise create a new peeled
* {@link ObjectIdRef} where Storage is set to PACKED.
*
- * @param f
+ * @param ref
+ * given ref
* @return a ref for Storage PACKED having the same name, id, peeledId as f
* @throws MissingObjectException
+ * if an object is missing
* @throws IOException
+ * if an IO error occurred
*/
- private Ref peeledPackedRef(Ref f)
+ private Ref peeledPackedRef(Ref ref)
throws MissingObjectException, IOException {
- if (f.getStorage().isPacked() && f.isPeeled()) {
- return f;
+ if (ref.getStorage().isPacked() && ref.isPeeled()) {
+ return ref;
}
- if (!f.isPeeled()) {
- f = peel(f);
+ if (!ref.isPeeled()) {
+ ref = peel(ref);
}
- ObjectId peeledObjectId = f.getPeeledObjectId();
+ ObjectId peeledObjectId = ref.getPeeledObjectId();
if (peeledObjectId != null) {
- return new ObjectIdRef.PeeledTag(PACKED, f.getName(),
- f.getObjectId(), peeledObjectId);
+ return new ObjectIdRef.PeeledTag(PACKED, ref.getName(),
+ ref.getObjectId(), peeledObjectId);
}
- return new ObjectIdRef.PeeledNonTag(PACKED, f.getName(),
- f.getObjectId());
+ return new ObjectIdRef.PeeledNonTag(PACKED, ref.getName(),
+ ref.getObjectId());
}
void log(boolean force, RefUpdate update, String msg, boolean deref)
@@ -928,7 +968,7 @@ public class RefDirectory extends RefDatabase {
PackedRefList getPackedRefs() throws IOException {
final PackedRefList curList = packedRefs.get();
- switch (trustPackedRefsStat) {
+ switch (coreConfig.getTrustPackedRefsStat()) {
case NEVER:
break;
case AFTER_OPEN:
@@ -944,12 +984,8 @@ public class RefDirectory extends RefDatabase {
return curList;
}
break;
- case UNSET:
- if (trustFolderStat
- && !curList.snapshot.isModified(packedRefsFile)) {
- return curList;
- }
- break;
+ case INHERIT:
+ // only used in CoreConfig internally
}
return refreshPackedRefs(curList);
@@ -1134,6 +1170,11 @@ public class RefDirectory extends RefDatabase {
LooseRef scanRef(LooseRef ref, String name) throws IOException {
final File path = fileFor(name);
+
+ if (coreConfig.getTrustLooseRefStat() == TrustStat.AFTER_OPEN) {
+ refreshPathToLooseRef(Paths.get(name));
+ }
+
FileSnapshot currentSnapshot = null;
if (ref != null) {
@@ -1219,6 +1260,29 @@ public class RefDirectory extends RefDatabase {
return new LooseUnpeeled(loose.snapshot, name, id);
}
+ /**
+ * Workaround for issues caused by NFS caching. Refresh directories starting
+ * from the repository root to a loose ref by opening an input stream. This
+ * refreshes file attributes of the loose ref (at least on some NFS
+ * clients).
+ *
+ * @param refPath
+ * path of a loose ref relative to the repository root
+ */
+ void refreshPathToLooseRef(Path refPath) {
+ for (int i = 1; i < refPath.getNameCount(); i++) {
+ File dir = fileFor(refPath.subpath(0, i).toString());
+ // Use Files.newInputStream(Path) as it is consistent with other
+ // code where a refresh is being done (see getPackedRefs()) and also
+ // it performs slightly better than Files.newDirectoryStream(Path)
+ try (InputStream stream = Files.newInputStream(dir.toPath())) {
+ // open the dir to refresh attributes (on some NFS clients)
+ } catch (IOException e) {
+ break; // loose ref may not exist
+ }
+ }
+ }
+
private static boolean isSymRef(byte[] buf, int n) {
if (n < 6)
return false;
@@ -1234,6 +1298,7 @@ public class RefDirectory extends RefDatabase {
*
* @return {@code true} if we are currently cloning a repository
* @throws IOException
+ * if an IO error occurred
*/
boolean isInClone() throws IOException {
return hasDanglingHead() && !packedRefsFile.exists() && !hasLooseRef();
@@ -1289,7 +1354,12 @@ public class RefDirectory extends RefDatabase {
name = name.substring(R_REFS.length());
return new File(refsDir, name);
}
- return new File(gitDir, name);
+ // HEAD needs to get resolved from git dir as resolving it from common dir
+ // would always lead back to current default branch
+ if (name.equals(HEAD)) {
+ return new File(gitDir, name);
+ }
+ return new File(gitCommonDir, name);
}
static int levelsIn(String name) {
@@ -1300,19 +1370,37 @@ public class RefDirectory extends RefDatabase {
}
static void delete(File file, int depth) throws IOException {
- delete(file, depth, null);
+ delete(file);
+ deleteEmptyParentDirs(file, depth);
}
- private static void delete(File file, int depth, LockFile rLck)
- throws IOException {
+ private static void delete(File file) throws IOException {
if (!file.delete() && file.isFile()) {
- throw new IOException(MessageFormat.format(
- JGitText.get().fileCannotBeDeleted, file));
+ throw new IOException(
+ MessageFormat.format(JGitText.get().fileCannotBeDeleted,
+ file));
+ }
+ }
+
+ private static void deleteAndUnlock(File file, int depth,
+ RefDirectoryUpdate refUpdate) throws IOException {
+ delete(file);
+ if (refUpdate != null) {
+ refUpdate.unlock(); // otherwise cannot delete parent directories emptied by the update
}
+ deleteEmptyParentDirs(file, depth);
+ }
+ private static void deleteAndUnlock(File file, int depth, LockFile rLck)
+ throws IOException {
+ delete(file);
if (rLck != null) {
- rLck.unlock(); // otherwise cannot delete dir below
+ rLck.unlock(); // otherwise cannot delete parent directories of the lock file
}
+ deleteEmptyParentDirs(file, depth);
+ }
+
+ private static void deleteEmptyParentDirs(File file, int depth) {
File dir = file.getParentFile();
for (int i = 0; i < depth; ++i) {
try {
@@ -1420,10 +1508,10 @@ public class RefDirectory extends RefDatabase {
implements LooseRef {
private final FileSnapshot snapShot;
- LoosePeeledTag(FileSnapshot snapshot, @NonNull String refName,
+ LoosePeeledTag(FileSnapshot snapShot, @NonNull String refName,
@NonNull ObjectId id, @NonNull ObjectId p) {
super(LOOSE, refName, id, p);
- this.snapShot = snapshot;
+ this.snapShot = snapShot;
}
@Override
@@ -1441,10 +1529,10 @@ public class RefDirectory extends RefDatabase {
implements LooseRef {
private final FileSnapshot snapShot;
- LooseNonTag(FileSnapshot snapshot, @NonNull String refName,
+ LooseNonTag(FileSnapshot snapShot, @NonNull String refName,
@NonNull ObjectId id) {
super(LOOSE, refName, id);
- this.snapShot = snapshot;
+ this.snapShot = snapShot;
}
@Override
@@ -1497,10 +1585,10 @@ public class RefDirectory extends RefDatabase {
LooseRef {
private final FileSnapshot snapShot;
- LooseSymbolicRef(FileSnapshot snapshot, @NonNull String refName,
+ LooseSymbolicRef(FileSnapshot snapShot, @NonNull String refName,
@NonNull Ref target) {
super(refName, target);
- this.snapShot = snapshot;
+ this.snapShot = snapShot;
}
@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java
index d07299e45a..1c5c48a296 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java
@@ -68,7 +68,6 @@ class RefDirectoryRename extends RefRename {
return refdb;
}
- /** {@inheritDoc} */
@Override
protected Result doRename() throws IOException {
if (source.getRef().isSymbolic())
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java
index 0dcb3196c5..436957bb24 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java
@@ -32,19 +32,16 @@ class RefDirectoryUpdate extends RefUpdate {
database = r;
}
- /** {@inheritDoc} */
@Override
protected RefDirectory getRefDatabase() {
return database;
}
- /** {@inheritDoc} */
@Override
protected Repository getRepository() {
return database.getRepository();
}
- /** {@inheritDoc} */
@Override
protected boolean tryLock(boolean deref) throws IOException {
shouldDeref = deref;
@@ -54,6 +51,7 @@ class RefDirectoryUpdate extends RefUpdate {
String name = dst.getName();
lock = new LockFile(database.fileFor(name));
if (lock.lock()) {
+ doAfterLocking(name);
dst = database.findRef(name);
setOldObjectId(dst != null ? dst.getObjectId() : null);
return true;
@@ -61,7 +59,6 @@ class RefDirectoryUpdate extends RefUpdate {
return false;
}
- /** {@inheritDoc} */
@Override
protected void unlock() {
if (lock != null) {
@@ -70,7 +67,6 @@ class RefDirectoryUpdate extends RefUpdate {
}
}
- /** {@inheritDoc} */
@Override
protected Result doUpdate(Result status) throws IOException {
WriteConfig wc = database.getRepository().getConfig()
@@ -112,7 +108,6 @@ class RefDirectoryUpdate extends RefUpdate {
}
}
- /** {@inheritDoc} */
@Override
protected Result doDelete(Result status) throws IOException {
if (getRef().getStorage() != Ref.Storage.NEW)
@@ -120,7 +115,6 @@ class RefDirectoryUpdate extends RefUpdate {
return status;
}
- /** {@inheritDoc} */
@Override
protected Result doLink(String target) throws IOException {
WriteConfig wc = database.getRepository().getConfig()
@@ -141,4 +135,14 @@ class RefDirectoryUpdate extends RefUpdate {
return Result.NEW;
return Result.FORCED;
}
+
+ /**
+ * Do any actions needed immediately after a lock on the ref is acquired
+ *
+ * @param name
+ * the name of the reference.
+ */
+ protected void doAfterLocking(String name) {
+ // No actions by default
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java
index cb80043b7e..6870d7686e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogEntryImpl.java
@@ -60,7 +60,6 @@ public class ReflogEntryImpl implements Serializable, ReflogEntry {
/* (non-Javadoc)
* @see org.eclipse.jgit.internal.storage.file.ReflogEntry#getOldId()
*/
- /** {@inheritDoc} */
@Override
public ObjectId getOldId() {
return oldId;
@@ -69,7 +68,6 @@ public class ReflogEntryImpl implements Serializable, ReflogEntry {
/* (non-Javadoc)
* @see org.eclipse.jgit.internal.storage.file.ReflogEntry#getNewId()
*/
- /** {@inheritDoc} */
@Override
public ObjectId getNewId() {
return newId;
@@ -78,7 +76,6 @@ public class ReflogEntryImpl implements Serializable, ReflogEntry {
/* (non-Javadoc)
* @see org.eclipse.jgit.internal.storage.file.ReflogEntry#getWho()
*/
- /** {@inheritDoc} */
@Override
public PersonIdent getWho() {
return who;
@@ -87,13 +84,11 @@ public class ReflogEntryImpl implements Serializable, ReflogEntry {
/* (non-Javadoc)
* @see org.eclipse.jgit.internal.storage.file.ReflogEntry#getComment()
*/
- /** {@inheritDoc} */
@Override
public String getComment() {
return comment;
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
@@ -104,7 +99,6 @@ public class ReflogEntryImpl implements Serializable, ReflogEntry {
/* (non-Javadoc)
* @see org.eclipse.jgit.internal.storage.file.ReflogEntry#parseCheckout()
*/
- /** {@inheritDoc} */
@Override
public CheckoutEntry parseCheckout() {
if (getComment().startsWith(CheckoutEntryImpl.CHECKOUT_MOVING_FROM)) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java
index 99a9e0938e..f1888eb90f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java
@@ -10,6 +10,8 @@
package org.eclipse.jgit.internal.storage.file;
+import static org.eclipse.jgit.lib.Constants.HEAD;
+
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -32,16 +34,19 @@ class ReflogReaderImpl implements ReflogReader {
/**
* @param db
+ * repository to read reflogs from
* @param refname
+ * {@code Ref} name
*/
ReflogReaderImpl(Repository db, String refname) {
- logName = new File(db.getDirectory(), Constants.LOGS + '/' + refname);
+ File logBaseDir = refname.equals(HEAD) ? db.getDirectory()
+ : db.getCommonDirectory();
+ logName = new File(logBaseDir, Constants.L_LOGS + refname);
}
/* (non-Javadoc)
* @see org.eclipse.jgit.internal.storage.file.ReflogReaader#getLastEntry()
*/
- /** {@inheritDoc} */
@Override
public ReflogEntry getLastEntry() throws IOException {
return getReverseEntry(0);
@@ -50,7 +55,6 @@ class ReflogReaderImpl implements ReflogReader {
/* (non-Javadoc)
* @see org.eclipse.jgit.internal.storage.file.ReflogReaader#getReverseEntries()
*/
- /** {@inheritDoc} */
@Override
public List<ReflogEntry> getReverseEntries() throws IOException {
return getReverseEntries(Integer.MAX_VALUE);
@@ -59,7 +63,6 @@ class ReflogReaderImpl implements ReflogReader {
/* (non-Javadoc)
* @see org.eclipse.jgit.internal.storage.file.ReflogReaader#getReverseEntry(int)
*/
- /** {@inheritDoc} */
@Override
public ReflogEntry getReverseEntry(int number) throws IOException {
if (number < 0)
@@ -89,7 +92,6 @@ class ReflogReaderImpl implements ReflogReader {
/* (non-Javadoc)
* @see org.eclipse.jgit.internal.storage.file.ReflogReaader#getReverseEntries(int)
*/
- /** {@inheritDoc} */
@Override
public List<ReflogEntry> getReverseEntries(int max) throws IOException {
final byte[] log;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java
index 7032083a4d..b1ceb14809 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogWriter.java
@@ -91,6 +91,7 @@ public class ReflogWriter {
* Create the log directories.
*
* @throws java.io.IOException
+ * if an IO error occurred
* @return this writer.
*/
public ReflogWriter create() throws IOException {
@@ -110,6 +111,7 @@ public class ReflogWriter {
* a {@link org.eclipse.jgit.lib.ReflogEntry} object.
* @return this writer
* @throws java.io.IOException
+ * if an IO error occurred
*/
public ReflogWriter log(String refName, ReflogEntry entry)
throws IOException {
@@ -132,6 +134,7 @@ public class ReflogWriter {
* reflog message
* @return this writer
* @throws java.io.IOException
+ * if an IO error occurred
*/
public ReflogWriter log(String refName, ObjectId oldId,
ObjectId newId, PersonIdent ident, String message) throws IOException {
@@ -150,6 +153,7 @@ public class ReflogWriter {
* whether to dereference symbolic refs
* @return this writer
* @throws java.io.IOException
+ * if an IO error occurred
*/
public ReflogWriter log(RefUpdate update, String msg,
boolean deref) throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataInput.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataInput.java
index 6a80519d0b..7a564cc6ab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataInput.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataInput.java
@@ -31,14 +31,12 @@ class SimpleDataInput implements DataInput {
this.fd = fd;
}
- /** {@inheritDoc} */
@Override
public int readInt() throws IOException {
readFully(buf, 0, 4);
return NB.decodeInt32(buf, 0);
}
- /** {@inheritDoc} */
@Override
public long readLong() throws IOException {
readFully(buf, 0, 8);
@@ -57,79 +55,66 @@ class SimpleDataInput implements DataInput {
return NB.decodeUInt32(buf, 0);
}
- /** {@inheritDoc} */
@Override
public void readFully(byte[] b) throws IOException {
readFully(b, 0, b.length);
}
- /** {@inheritDoc} */
@Override
public void readFully(byte[] b, int off, int len) throws IOException {
IO.readFully(fd, b, off, len);
}
- /** {@inheritDoc} */
@Override
public int skipBytes(int n) throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public boolean readBoolean() throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public byte readByte() throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public int readUnsignedByte() throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public short readShort() throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public int readUnsignedShort() throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public char readChar() throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public float readFloat() throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public double readDouble() throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public String readLine() throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public String readUTF() throws IOException {
throw new UnsupportedOperationException();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java
index d01f6b4bda..74840889ce 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SimpleDataOutput.java
@@ -31,88 +31,74 @@ class SimpleDataOutput implements DataOutput {
this.fd = fd;
}
- /** {@inheritDoc} */
@Override
public void writeShort(int v) throws IOException {
NB.encodeInt16(buf, 0, v);
fd.write(buf, 0, 2);
}
- /** {@inheritDoc} */
@Override
public void writeInt(int v) throws IOException {
NB.encodeInt32(buf, 0, v);
fd.write(buf, 0, 4);
}
- /** {@inheritDoc} */
@Override
public void writeLong(long v) throws IOException {
NB.encodeInt64(buf, 0, v);
fd.write(buf, 0, 8);
}
- /** {@inheritDoc} */
@Override
public void write(int b) throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public void write(byte[] b) throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public void write(byte[] b, int off, int len) throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public void writeBoolean(boolean v) throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public void writeByte(int v) throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public void writeChar(int v) throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public void writeFloat(float v) throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public void writeDouble(double v) throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public void writeBytes(String s) throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public void writeChars(String s) throws IOException {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public void writeUTF(String s) throws IOException {
throw new UnsupportedOperationException();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SnapshottingRefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SnapshottingRefDirectory.java
index 0b9748096e..1dc5776e06 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SnapshottingRefDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/SnapshottingRefDirectory.java
@@ -13,19 +13,24 @@ package org.eclipse.jgit.internal.storage.file;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.revwalk.RevWalk;
+import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* Snapshotting write-through cache of a {@link RefDirectory}.
* <p>
* This is intended to be short-term write-through snapshot based cache used in
- * a request scope to avoid re-reading packed-refs on each read. A future
- * improvement could also snapshot loose refs.
+ * a request scope to avoid re-reading packed-refs on each read and to avoid
+ * refreshing paths to a loose ref that has already been refreshed.
* <p>
* Only use this class when concurrent writes from other requests (not using the
* same instance of SnapshottingRefDirectory) generally need not be visible to
@@ -35,6 +40,7 @@ import java.util.List;
*/
class SnapshottingRefDirectory extends RefDirectory {
final RefDirectory refDb;
+ private final Set<File> refreshedLooseRefDirs = ConcurrentHashMap.newKeySet();
private volatile boolean isValid;
@@ -67,14 +73,28 @@ class SnapshottingRefDirectory extends RefDirectory {
return packedRefs.get();
}
- /** {@inheritDoc} */
+ @Override
+ void refreshPathToLooseRef(Path refPath) {
+ for (int i = 1; i < refPath.getNameCount(); i++) {
+ File dir = fileFor(refPath.subpath(0, i).toString());
+ if (!refreshedLooseRefDirs.contains(dir)) {
+ try (InputStream stream = Files.newInputStream(dir.toPath())) {
+ // open the dir to refresh attributes (on some NFS clients)
+ } catch (IOException e) {
+ break; // loose ref may not exist
+ } finally {
+ refreshedLooseRefDirs.add(dir);
+ }
+ }
+ }
+ }
+
@Override
void delete(RefDirectoryUpdate update) throws IOException {
refreshSnapshot();
super.delete(update);
}
- /** {@inheritDoc} */
@Override
public RefDirectoryUpdate newUpdate(String name, boolean detach)
throws IOException {
@@ -82,19 +102,16 @@ class SnapshottingRefDirectory extends RefDirectory {
return super.newUpdate(name, detach);
}
- /** {@inheritDoc} */
@Override
public PackedBatchRefUpdate newBatchUpdate() {
return new SnapshotPackedBatchRefUpdate(this);
}
- /** {@inheritDoc} */
@Override
public PackedBatchRefUpdate newBatchUpdate(boolean shouldLockLooseRefs) {
return new SnapshotPackedBatchRefUpdate(this, shouldLockLooseRefs);
}
- /** {@inheritDoc} */
@Override
RefDirectoryUpdate newTemporaryUpdate() throws IOException {
refreshSnapshot();
@@ -113,6 +130,7 @@ class SnapshottingRefDirectory extends RefDirectory {
}
synchronized void invalidateSnapshot() {
+ refreshedLooseRefDirs.clear();
isValid = false;
}
@@ -126,6 +144,7 @@ class SnapshottingRefDirectory extends RefDirectory {
* threads use this snapshot.
*
* @throws IOException
+ * if an IO error occurred
*/
private synchronized void refreshSnapshot() throws IOException {
compareAndSetPackedRefs(packedRefs.get(), refDb.getPackedRefs());
@@ -148,29 +167,29 @@ class SnapshottingRefDirectory extends RefDirectory {
}
private static <T> T invalidateSnapshotOnError(
- SupplierThrowsException<T, IOException> f, RefDatabase refDb)
+ SupplierThrowsException<T, IOException> f, SnapshottingRefDirectory refDb)
throws IOException {
return invalidateSnapshotOnError(a -> f.call(), null, refDb);
}
private static <A, R> R invalidateSnapshotOnError(
FunctionThrowsException<A, R, IOException> f, A a,
- RefDatabase refDb) throws IOException {
+ SnapshottingRefDirectory refDb) throws IOException {
try {
return f.apply(a);
} catch (IOException e) {
- ((SnapshottingRefDirectory) refDb).invalidateSnapshot();
+ refDb.invalidateSnapshot();
throw e;
}
}
private static <A1, A2, A3> void invalidateSnapshotOnError(
TriConsumerThrowsException<A1, A2, A3, IOException> f, A1 a1, A2 a2,
- A3 a3, RefDatabase refDb) throws IOException {
+ A3 a3, SnapshottingRefDirectory refDb) throws IOException {
try {
f.accept(a1, a2, a3);
} catch (IOException e) {
- ((SnapshottingRefDirectory) refDb).invalidateSnapshot();
+ refDb.invalidateSnapshot();
throw e;
}
}
@@ -182,39 +201,57 @@ class SnapshottingRefDirectory extends RefDirectory {
@Override
public Result forceUpdate() throws IOException {
- return invalidateSnapshotOnError(() -> super.forceUpdate(),
+ return invalidateSnapshotOnError(super::forceUpdate,
getRefDatabase());
}
@Override
public Result update() throws IOException {
- return invalidateSnapshotOnError(() -> super.update(),
- getRefDatabase());
+ return invalidateSnapshotOnError(super::update, getRefDatabase());
}
@Override
public Result update(RevWalk walk) throws IOException {
- return invalidateSnapshotOnError(rw -> super.update(rw), walk,
+ return invalidateSnapshotOnError(super::update, walk,
getRefDatabase());
}
@Override
public Result delete() throws IOException {
- return invalidateSnapshotOnError(() -> super.delete(),
- getRefDatabase());
+ return invalidateSnapshotOnError(super::delete, getRefDatabase());
}
@Override
public Result delete(RevWalk walk) throws IOException {
- return invalidateSnapshotOnError(rw -> super.delete(rw), walk,
+ return invalidateSnapshotOnError(super::delete, walk,
getRefDatabase());
}
@Override
public Result link(String target) throws IOException {
- return invalidateSnapshotOnError(t -> super.link(t), target,
+ return invalidateSnapshotOnError(super::link, target,
getRefDatabase());
}
+
+ /**
+ * Invalidate the SnapshottingRefDirectory snapshot after locking the
+ * ref.
+ * <p>
+ * Doing this after locking the ref ensures that the upcoming write is
+ * not based on a cached value.
+ *
+ * @param name
+ * the name of the reference.
+ */
+ @Override
+ protected void doAfterLocking(String name) {
+ getRefDatabase().invalidateSnapshot();
+ }
+
+ @Override
+ public SnapshottingRefDirectory getRefDatabase() {
+ return (SnapshottingRefDirectory) super.getRefDatabase();
+ }
}
private static class SnapshotRefDirectoryRename extends RefDirectoryRename {
@@ -225,26 +262,30 @@ class SnapshottingRefDirectory extends RefDirectory {
@Override
public RefUpdate.Result rename() throws IOException {
- return invalidateSnapshotOnError(() -> super.rename(),
- getRefDirectory());
+ return invalidateSnapshotOnError(super::rename, getRefDirectory());
+ }
+
+ @Override
+ public SnapshottingRefDirectory getRefDirectory() {
+ return (SnapshottingRefDirectory) super.getRefDirectory();
}
}
private static class SnapshotPackedBatchRefUpdate
extends PackedBatchRefUpdate {
- SnapshotPackedBatchRefUpdate(RefDirectory refdb) {
- super(refdb);
+ SnapshotPackedBatchRefUpdate(RefDirectory refDb) {
+ super(refDb);
}
- SnapshotPackedBatchRefUpdate(RefDirectory refdb,
+ SnapshotPackedBatchRefUpdate(RefDirectory refDb,
boolean shouldLockLooseRefs) {
- super(refdb, shouldLockLooseRefs);
+ super(refDb, shouldLockLooseRefs);
}
@Override
public void execute(RevWalk walk, ProgressMonitor monitor,
List<String> options) throws IOException {
- invalidateSnapshotOnError((rw, m, o) -> super.execute(rw, m, o),
+ invalidateSnapshotOnError(super::execute,
walk, monitor, options, getRefDatabase());
}
@@ -254,5 +295,10 @@ class SnapshottingRefDirectory extends RefDirectory {
invalidateSnapshotOnError((rw, m, a3) -> super.execute(rw, m), walk,
monitor, null, getRefDatabase());
}
+
+ @Override
+ public SnapshottingRefDirectory getRefDatabase() {
+ return (SnapshottingRefDirectory) super.getRefDatabase();
+ }
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
index 25653b3ce3..15c125c684 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCache.java
@@ -15,8 +15,10 @@ import java.io.IOException;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Collections;
+import java.util.HashSet;
import java.util.Map;
import java.util.Random;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -373,6 +375,8 @@ public class WindowCache {
}
/**
+ * Get singleton instance
+ *
* @return the cached instance.
*/
public static WindowCache getInstance() {
@@ -395,7 +399,11 @@ public class WindowCache {
}
static final void purge(Pack pack) {
- cache.removeAll(pack);
+ purge(Collections.singleton(pack));
+ }
+
+ static final void purge(Set<Pack> packs) {
+ cache.queueRemoveAll(packs);
}
/** cleanup released and/or garbage collected windows. */
@@ -435,7 +443,32 @@ public class WindowCache {
private final AtomicBoolean publishMBean = new AtomicBoolean();
- private boolean useStrongRefs;
+ private final boolean useStrongRefs;
+
+ private final boolean useStrongIndexRefs;
+
+ /** Removers are purely CPU/mem bound (no I/O), so likely should not go above # CPUs */
+ private final int idealNumRemovers;
+
+ /** Number of blocks to split the Pack removal into.
+ *
+ * Consolidation is better with more blocks since it increases
+ * the wait before moving to the next set by allowing more work
+ * to accumulate in the next set. On the flip side, the more
+ * blocks, the more synchronization overhead increasing each
+ * removers latency.
+ */
+ private final int numRemovalBlocks;
+
+ private final int removalBlockSize;
+
+ private Set<Pack> packsToRemove = new HashSet<>();
+
+ private Set<Pack> packsBeingRemoved;
+
+ private int numRemovers;
+
+ private int blockBeingRemoved;
private WindowCache(WindowCacheConfig cfg) {
tableSize = tableSize(cfg);
@@ -467,6 +500,7 @@ public class WindowCache {
windowSizeShift = bits(cfg.getPackedGitWindowSize());
windowSize = 1 << windowSizeShift;
useStrongRefs = cfg.isPackedGitUseStrongRefs();
+ useStrongIndexRefs = cfg.isPackedIndexGitUseStrongRefs();
queue = useStrongRefs ? new StrongCleanupQueue(this)
: new SoftCleanupQueue(this);
@@ -474,6 +508,23 @@ public class WindowCache {
statsRecorder = mbean;
publishMBean.set(cfg.getExposeStatsViaJmx());
+ /* Since each worker will only process up to one full set of blocks, at least 2
+ * workers are needed anytime there are queued removals to ensure that all the
+ * blocks will get processed. However, if workers are maxed out at only 2, then
+ * enough newer workers will never start in order to make it safe for older
+ * workers to quit early. At least 3 workers are needed to make older worker
+ * relief transitions possible.
+ */
+ idealNumRemovers = Math.max(3, Runtime.getRuntime().availableProcessors());
+
+ int bs = 1024;
+ if (tableSize < 2 * bs) {
+ bs = tableSize / 2;
+ }
+ removalBlockSize = bs;
+ numRemovalBlocks = tableSize / removalBlockSize;
+ blockBeingRemoved = numRemovalBlocks - 1;
+
if (maxFiles < 1)
throw new IllegalArgumentException(JGitText.get().openFilesMustBeAtLeast1);
if (maxBytes < windowSize)
@@ -488,6 +539,8 @@ public class WindowCache {
}
/**
+ * Get cache statistics
+ *
* @return cache statistics for the WindowCache
*/
public WindowCacheStats getStats() {
@@ -701,22 +754,105 @@ public class WindowCache {
}
/**
- * Clear all entries related to a single file.
+ * Asynchronously clear all entries related to files.
* <p>
- * Typically this method is invoked during {@link Pack#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.
+ * Typically this method is invoked during {@link Pack#close()}, or
+ * {@link Pack#close(Set)}, when we know the packs are never going to be
+ * useful to us again (for example, they no longer exist on disk after gc).
+ * A concurrent reader loading an entry from these same packs may cause a
+ * pack to become stuck in the cache anyway.
*
- * @param pack
- * the file to purge all entries of.
+ * Work on clearing files will be split up into blocks so that removing
+ * can be shared by more than one thread. This potential work sharing
+ * can provide 2 optimizations for removals:
+ * <ol>
+ * <li> It provides an opportunity for separate removal requests to be
+ * consolidated into one removal pass.</li>
+ * <li> It can reduce removing thread latencies by sharing the removal work
+ * with other removing threads which otherwise might not have any work to do
+ * due to their removal request being consolidated. This makes the system
+ * more efficient and can actually reduce latencies as system load increases
+ * due to pack removals!</li>
+ * </ol>
+ * The optimizations above are all achieved without blockng threads to wait
+ * for other threads to complete (although naturally there are some
+ * synchronization points), and while ensuring that no threads do more work
+ * than if they were the only thread available to perform a removal.
+ *
+ * @param packs
+ * the files to purge all entries of
*/
- private void removeAll(Pack pack) {
- for (int s = 0; s < tableSize; s++) {
+ private void queueRemoveAll(Set<Pack> packs) {
+ synchronized (this) {
+ packsToRemove.addAll(packs);
+ if (numRemovers >= idealNumRemovers) {
+ return;
+ }
+ numRemovers++;
+ }
+ for (int numRemoved = 0; removeNextBlock(numRemoved); numRemoved++) {
+ // empty
+ }
+ synchronized (this) {
+ if (numRemovers > 0) {
+ numRemovers--;
+ }
+ }
+ }
+
+ /** Determine which block to remove next, if any, and do so.
+ *
+ * @param numRemoved
+ * the number of already processed block removals by the current thread
+ * @return whether more processing should be done by the current thread
+ */
+ private boolean removeNextBlock(int numRemoved) {
+ Set<Pack> toRemove;
+ int block;
+ synchronized (this) {
+ if (packsBeingRemoved == null || blockBeingRemoved >= numRemovalBlocks - 1) {
+ if (packsToRemove.isEmpty()) {
+ return false;
+ }
+
+ blockBeingRemoved = 0;
+ packsBeingRemoved = packsToRemove;
+ packsToRemove = new HashSet<>();
+ } else {
+ blockBeingRemoved++;
+ }
+
+ toRemove = packsBeingRemoved;
+ block = blockBeingRemoved;
+ }
+
+ removeBlock(toRemove, block);
+ numRemoved++;
+
+ /* Ensure threads never work on more than a full set of blocks (the equivalent
+ * of removing one Pack) */
+ boolean isLast = numRemoved >= numRemovalBlocks;
+ synchronized (this) {
+ if (numRemovers >= idealNumRemovers) {
+ isLast = true;
+ }
+ }
+ return !isLast;
+ }
+
+ /** Remove a block of entries for a Set of files
+ * @param packs
+ * the files to purge all entries of
+ * @param block
+ * the specific block to process removals for
+ */
+ private void removeBlock(Set<Pack> packs, int block) {
+ int starting = block * removalBlockSize;
+ for (int s = starting; s < starting + removalBlockSize && s < tableSize; s++) {
final Entry e1 = table.get(s);
boolean hasDead = false;
for (Entry e = e1; e != null; e = e.next) {
- if (e.ref.getPack() == pack) {
+ if (packs.contains(e.ref.getPack())) {
e.kill();
hasDead = true;
} else if (e.dead)
@@ -751,6 +887,10 @@ public class WindowCache {
return n == top.next ? top : new Entry(n, top.ref);
}
+ boolean isPackedIndexGitUseStrongRefs() {
+ return useStrongIndexRefs;
+ }
+
private static class Entry {
/** Next entry in the hash table's chain list. */
final Entry next;
@@ -841,6 +981,7 @@ public class WindowCache {
* Whether this is a strong reference.
* @return {@code true} if this is a strong reference
*/
+ @SuppressWarnings("unused")
boolean isStrongRef();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java
index fa743babe7..33459ef18d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java
@@ -36,6 +36,7 @@ import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.BitmapIndex;
import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
import org.eclipse.jgit.internal.storage.commitgraph.CommitGraph;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.InflaterCache;
import org.eclipse.jgit.lib.ObjectId;
@@ -49,12 +50,16 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
/** Temporary buffer large enough for at least one raw object id. */
final byte[] tempId = new byte[Constants.OBJECT_ID_LENGTH];
+ private final boolean useObjectSizeIndex;
+
private Inflater inf;
private ByteWindow window;
private DeltaBaseCache baseCache;
+ private Pack lastPack;
+
@Nullable
private final ObjectInserter createdFromInserter;
@@ -64,6 +69,10 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
this.db = db;
this.createdFromInserter = null;
this.streamFileThreshold = WindowCache.getStreamFileThreshold();
+ this.useObjectSizeIndex = db == null ? false
+ : db.getConfig().getBoolean(
+ ConfigConstants.CONFIG_PACK_SECTION,
+ ConfigConstants.CONFIG_KEY_USE_OBJECT_SIZE_INDEX, false);
}
WindowCursor(FileObjectDatabase db,
@@ -71,6 +80,10 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
this.db = db;
this.createdFromInserter = createdFromInserter;
this.streamFileThreshold = WindowCache.getStreamFileThreshold();
+ this.useObjectSizeIndex = db == null ? false
+ : db.getConfig().getBoolean(
+ ConfigConstants.CONFIG_PACK_SECTION,
+ ConfigConstants.CONFIG_KEY_USE_OBJECT_SIZE_INDEX, false);
}
DeltaBaseCache getDeltaBaseCache() {
@@ -79,13 +92,11 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
return baseCache;
}
- /** {@inheritDoc} */
@Override
public ObjectReader newReader() {
return new WindowCursor(db);
}
- /** {@inheritDoc} */
@Override
public BitmapIndex getBitmapIndex() throws IOException {
for (Pack pack : db.getPacks()) {
@@ -96,13 +107,11 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
return null;
}
- /** {@inheritDoc} */
@Override
public Optional<CommitGraph> getCommitGraph() {
return db.getCommitGraph();
}
- /** {@inheritDoc} */
@Override
public Collection<CachedPack> getCachedPacksAndUpdate(
BitmapBuilder needBitmap) throws IOException {
@@ -115,7 +124,6 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
return Collections.emptyList();
}
- /** {@inheritDoc} */
@Override
public Collection<ObjectId> resolve(AbbreviatedObjectId id)
throws IOException {
@@ -126,13 +134,11 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
return matches;
}
- /** {@inheritDoc} */
@Override
public boolean has(AnyObjectId objectId) throws IOException {
return db.has(objectId);
}
- /** {@inheritDoc} */
@Override
public ObjectLoader open(AnyObjectId objectId, int typeHint)
throws MissingObjectException, IncorrectObjectTypeException,
@@ -149,15 +155,12 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
return ldr;
}
- /** {@inheritDoc} */
@Override
public Set<ObjectId> getShallowCommits() throws IOException {
return db.getShallowCommits();
}
- /** {@inheritDoc} */
- @Override
- public long getObjectSize(AnyObjectId objectId, int typeHint)
+ private long getObjectSizeStorage(AnyObjectId objectId, int typeHint)
throws MissingObjectException, IncorrectObjectTypeException,
IOException {
long sz = db.getObjectSize(this, objectId);
@@ -170,13 +173,68 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
return sz;
}
- /** {@inheritDoc} */
+ @Override
+ public long getObjectSize(AnyObjectId objectId, int typeHint)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ // Async queue uses hint OBJ_ANY
+ if (typeHint != Constants.OBJ_BLOB) {
+ return getObjectSizeStorage(objectId, typeHint);
+ }
+
+ Pack pack = findPack(objectId);
+ if (pack == null) {
+ // Non-packed object (e.g. loose or in alternates)
+ return getObjectSizeStorage(objectId, typeHint);
+ }
+
+ if (useObjectSizeIndex && pack.hasObjectSizeIndex()) {
+ long sz = pack.getIndexedObjectSize(objectId);
+ if (sz >= 0) {
+ return sz;
+ }
+ }
+ return getObjectSizeStorage(objectId, typeHint);
+ }
+
+ @Override
+ public boolean isNotLargerThan(AnyObjectId objectId, int typeHint,
+ long size)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ if (typeHint != Constants.OBJ_BLOB) {
+ return getObjectSizeStorage(objectId, typeHint) <= size;
+ }
+
+ Pack pack = findPack(objectId);
+ if (pack == null || !pack.hasObjectSizeIndex()) {
+ // Non-packed object (e.g. loose or in alternates)
+ return getObjectSizeStorage(objectId, typeHint) <= size;
+ }
+
+ return pack.getIndexedObjectSize(objectId) <= size;
+ }
+
+ private Pack findPack(AnyObjectId objectId) throws IOException {
+ if (lastPack != null && lastPack.hasObject(objectId)) {
+ return lastPack;
+ }
+
+ for (Pack p : db.getPacks()) {
+ if (p.hasObject(objectId)) {
+ lastPack = p;
+ return p;
+ }
+ }
+
+ return null;
+ }
+
@Override
public LocalObjectToPack newObjectToPack(AnyObjectId objectId, int type) {
return new LocalObjectToPack(objectId, type);
}
- /** {@inheritDoc} */
@Override
public void selectObjectRepresentation(PackWriter packer,
ProgressMonitor monitor, Iterable<ObjectToPack> objects)
@@ -187,7 +245,6 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
}
}
- /** {@inheritDoc} */
@Override
public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp,
boolean validate) throws IOException,
@@ -196,7 +253,6 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
src.pack.copyAsIs(out, src, validate, this);
}
- /** {@inheritDoc} */
@Override
public void writeObjects(PackOutputStream out, List<ObjectToPack> list)
throws IOException {
@@ -218,7 +274,7 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
* @param cnt
* number of bytes to copy. This value may exceed the number of
* bytes remaining in the window starting at offset
- * <code>pos</code>.
+ * <code>position</code>.
* @return number of bytes actually copied; this may be less than
* <code>cnt</code> if <code>cnt</code> exceeded the number of bytes
* available.
@@ -240,7 +296,6 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
return cnt - need;
}
- /** {@inheritDoc} */
@Override
public void copyPackAsIs(PackOutputStream out, CachedPack pack)
throws IOException {
@@ -336,7 +391,6 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
}
}
- /** {@inheritDoc} */
@Override
@Nullable
public ObjectInserter getCreatedFromInserter() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/BlockSource.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/BlockSource.java
index 82d07e6bde..58695fc9ab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/BlockSource.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/BlockSource.java
@@ -147,7 +147,6 @@ public abstract class BlockSource implements AutoCloseable {
// Do nothing by default.
}
- /** {@inheritDoc} */
@Override
public abstract void close();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStream.java
index ca2095feec..ce86eabe90 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/io/CancellableDigestOutputStream.java
@@ -80,7 +80,6 @@ public class CancellableDigestOutputStream extends OutputStream {
return count;
}
- /** {@inheritDoc} */
@Override
public final void write(int b) throws IOException {
if (checkCancelAt <= count) {
@@ -95,7 +94,6 @@ public class CancellableDigestOutputStream extends OutputStream {
count++;
}
- /** {@inheritDoc} */
@Override
public final void write(byte[] b, int off, int len) throws IOException {
while (0 < len) {
@@ -116,7 +114,6 @@ public class CancellableDigestOutputStream extends OutputStream {
}
}
- /** {@inheritDoc} */
@Override
public void flush() throws IOException {
out.flush();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/memory/TernarySearchTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/memory/TernarySearchTree.java
index 1ac6627360..72434dbffe 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/memory/TernarySearchTree.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/memory/TernarySearchTree.java
@@ -9,9 +9,9 @@
*/
package org.eclipse.jgit.internal.storage.memory;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -300,6 +300,7 @@ public final class TernarySearchTree<Value> {
* Find the key which is the longest prefix of the given query string.
*
* @param query
+ * the query string
* @return the key which is the longest prefix of the given query string or
* {@code null} if none exists.
*/
@@ -339,7 +340,7 @@ public final class TernarySearchTree<Value> {
* @return all keys
*/
public Iterable<String> getKeys() {
- Queue<String> queue = new LinkedList<>();
+ Queue<String> queue = new ArrayDeque<>();
lock.readLock().lock();
try {
findKeysWithPrefix(root, new StringBuilder(), queue);
@@ -357,7 +358,7 @@ public final class TernarySearchTree<Value> {
* @return keys starting with given prefix
*/
public Iterable<String> getKeysWithPrefix(String prefix) {
- Queue<String> keys = new LinkedList<>();
+ Queue<String> keys = new ArrayDeque<>();
if (prefix == null) {
return keys;
}
@@ -485,7 +486,7 @@ public final class TernarySearchTree<Value> {
* @return keys matching given pattern.
*/
public Iterable<String> getKeysMatching(String pattern) {
- Queue<String> keys = new LinkedList<>();
+ Queue<String> keys = new ArrayDeque<>();
lock.readLock().lock();
try {
findKeysWithPrefix(root, new StringBuilder(), 0, pattern, keys);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndex.java
new file mode 100644
index 0000000000..15b52391b8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndex.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024, GerritForge Inc. and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.midx;
+
+import java.util.Set;
+
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * An index over multiple packs
+ */
+public interface MultiPackIndex {
+
+ /**
+ * Obtain the array of packfiles in the MultiPackIndex.
+ * <p>
+ * The pack ids correspond to positions in this list.
+ *
+ * @return array of packnames refered in this multipak index
+ */
+ String[] getPackNames();
+
+ /**
+ * Does this index contains the object
+ *
+ * @param oid
+ * object id
+ * @return true of the index knows this the object
+ */
+ boolean hasObject(AnyObjectId oid);
+
+ /**
+ * Obtain the location of the object.
+ * <p>
+ * The returned object can be reused by the implementations. Callers
+ * must create a #copy() if they want to keep a reference.
+ *
+ * @param objectId
+ * objectId to read.
+ * @return mutable instance with the location or null if not found.
+ */
+ PackOffset find(AnyObjectId objectId);
+
+ /**
+ * Find objects matching the prefix abbreviation.
+ *
+ * @param matches
+ * set to add any located ObjectIds to. This is an output
+ * parameter.
+ * @param id
+ * prefix to search for.
+ * @param matchLimit
+ * maximum number of results to return. At most this many
+ * ObjectIds should be added to matches before returning.
+ */
+ void resolve(Set<ObjectId> matches, AbbreviatedObjectId id, int matchLimit);
+
+ /**
+ * Memory size of this multipack index
+ *
+ * @return size of this multipack index in memory, in bytes
+ */
+ long getMemorySize();
+
+ /**
+ * (packId, offset) coordinates of an object
+ */
+ class PackOffset {
+
+ int packId;
+
+ long offset;
+
+ protected PackOffset setValues(int packId, long offset) {
+ this.packId = packId;
+ this.offset = offset;
+ return this;
+ }
+
+ public int getPackId() {
+ return packId;
+ }
+
+ public long getOffset() {
+ return offset;
+ }
+
+ public PackOffset copy() {
+ PackOffset copy = new PackOffset();
+ return copy.setValues(this.packId, this.offset);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexConstants.java
new file mode 100644
index 0000000000..5d86f44baf
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexConstants.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2025, Google LLC
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+class MultiPackIndexConstants {
+ static final int MIDX_SIGNATURE = 0x4d494458; /* MIDX */
+
+ static final byte MIDX_VERSION = 1;
+
+ /**
+ * We infer the length of object IDs (OIDs) from this value:
+ *
+ * <pre>
+ * 1 => SHA-1
+ * 2 => SHA-256
+ * </pre>
+ */
+ static final byte OID_HASH_VERSION = 1;
+
+ static final int MULTIPACK_INDEX_FANOUT_SIZE = 4 * 256;
+
+ /**
+ * First 4 bytes describe the chunk id. Value 0 is a terminating label.
+ * Other 8 bytes provide the byte-offset in current file for chunk to start.
+ */
+ static final int CHUNK_LOOKUP_WIDTH = 12;
+
+ /** "PNAM" chunk */
+ static final int MIDX_CHUNKID_PACKNAMES = 0x504e414d;
+
+ /** "OIDF" chunk */
+ static final int MIDX_CHUNKID_OIDFANOUT = 0x4f494446;
+
+ /** "OIDL" chunk */
+ static final int MIDX_CHUNKID_OIDLOOKUP = 0x4f49444c;
+
+ /** "OOFF" chunk */
+ static final int MIDX_CHUNKID_OBJECTOFFSETS = 0x4f4f4646;
+
+ /** "LOFF" chunk */
+ static final int MIDX_CHUNKID_LARGEOFFSETS = 0x4c4f4646;
+
+ /** "RIDX" chunk */
+ static final int MIDX_CHUNKID_REVINDEX = 0x52494458;
+
+ /** "BTMP" chunk */
+ static final int MIDX_CHUNKID_BITMAPPEDPACKS = 0x42544D50;
+
+ private MultiPackIndexConstants() {
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexLoader.java
new file mode 100644
index 0000000000..61caddc221
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexLoader.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2024, GerritForge Inc. and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.midx;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.CHUNK_LOOKUP_WIDTH;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_BITMAPPEDPACKS;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_LARGEOFFSETS;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OBJECTOFFSETS;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OIDFANOUT;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OIDLOOKUP;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_PACKNAMES;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_REVINDEX;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_SIGNATURE;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.NB;
+import org.eclipse.jgit.util.io.SilentFileInputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The loader returns the representation of the MultiPackIndex file content.
+ */
+public class MultiPackIndexLoader {
+ private final static Logger LOG = LoggerFactory
+ .getLogger(MultiPackIndexLoader.class);
+
+ /**
+ * Open an existing MultiPackIndex file for reading.
+ * <p>
+ * The format of the file will be automatically detected and a proper access
+ * implementation for that format will be constructed and returned to the
+ * caller. The file may or may not be held open by the returned instance.
+ *
+ * @param midxFile
+ * existing multi-pack-index to read.
+ * @return a copy of the multi-pack-index file in memory
+ * @throws FileNotFoundException
+ * the file does not exist.
+ * @throws MultiPackIndexFormatException
+ * MultiPackIndex file's format is different from we expected.
+ * @throws java.io.IOException
+ * the file exists but could not be read due to security errors
+ * or unexpected data corruption.
+ */
+ public static MultiPackIndex open(File midxFile)
+ throws FileNotFoundException, MultiPackIndexFormatException,
+ IOException {
+ try (SilentFileInputStream fd = new SilentFileInputStream(midxFile)) {
+ try {
+ return read(fd);
+ } catch (MultiPackIndexFormatException fe) {
+ throw fe;
+ } catch (IOException ioe) {
+ throw new IOException(
+ MessageFormat.format(JGitText.get().unreadableMIDX,
+ midxFile.getAbsolutePath()),
+ ioe);
+ }
+ }
+ }
+
+ /**
+ * Read an existing MultiPackIndex file from a buffered stream.
+ * <p>
+ * The format of the file will be automatically detected and a proper access
+ * implementation for that format will be constructed and returned to the
+ * caller. The file may or may not be held open by the returned instance.
+ *
+ * @param fd
+ * stream to read the multipack-index file from. The stream must be
+ * buffered as some small IOs are performed against the stream.
+ * The caller is responsible for closing the stream.
+ * @return a copy of the MultiPackIndex file in memory
+ * @throws MultiPackIndexFormatException
+ * the MultiPackIndex file's format is different from we
+ * expected.
+ * @throws java.io.IOException
+ * the stream cannot be read.
+ */
+ public static MultiPackIndex read(InputStream fd)
+ throws MultiPackIndexFormatException, IOException {
+ byte[] hdr = new byte[12];
+ IO.readFully(fd, hdr, 0, hdr.length);
+
+ int magic = NB.decodeInt32(hdr, 0);
+
+ if (magic != MIDX_SIGNATURE) {
+ throw new MultiPackIndexFormatException(JGitText.get().notAMIDX);
+ }
+
+ // Check MultiPackIndex version
+ int v = hdr[4];
+ if (v != 1) {
+ throw new MultiPackIndexFormatException(MessageFormat
+ .format(JGitText.get().unsupportedMIDXVersion, v));
+ }
+
+ // Read the object Id version (1 byte)
+ // 1 => SHA-1
+ // 2 => SHA-256
+ // TODO: If the hash type does not match the repository's hash
+ // algorithm,
+ // the multi-pack-index file should be ignored with a warning
+ // presented to the user.
+ int commitIdVersion = hdr[5];
+ if (commitIdVersion != 1) {
+ throw new MultiPackIndexFormatException(
+ JGitText.get().incorrectOBJECT_ID_LENGTH);
+ }
+
+ // Read the number of "chunkOffsets" (1 byte)
+ int chunkCount = hdr[6];
+
+ // Read the number of multi-pack-index files (1 byte)
+ // This value is currently always zero.
+ // TODO populate this
+ // int numberOfMultiPackIndexFiles = hdr[7];
+
+ // Number of packfiles (4 bytes)
+ int packCount = NB.decodeInt32(hdr, 8);
+
+ byte[] lookupBuffer = new byte[CHUNK_LOOKUP_WIDTH * (chunkCount + 1)];
+
+ IO.readFully(fd, lookupBuffer, 0, lookupBuffer.length);
+
+ List<ChunkSegment> chunks = new ArrayList<>(chunkCount + 1);
+ for (int i = 0; i <= chunkCount; i++) {
+ // chunks[chunkCount] is just a marker, in order to record the
+ // length of the last chunk.
+ int id = NB.decodeInt32(lookupBuffer, i * 12);
+ long offset = NB.decodeInt64(lookupBuffer, i * 12 + 4);
+ chunks.add(new ChunkSegment(id, offset));
+ }
+
+ MultiPackIndexBuilder builder = MultiPackIndexBuilder.builder();
+ builder.setPackCount(packCount);
+ for (int i = 0; i < chunkCount; i++) {
+ long chunkOffset = chunks.get(i).offset;
+ int chunkId = chunks.get(i).id;
+ long len = chunks.get(i + 1).offset - chunkOffset;
+
+ if (len > Integer.MAX_VALUE - 8) { // http://stackoverflow.com/a/8381338
+ throw new MultiPackIndexFormatException(
+ JGitText.get().multiPackIndexFileIsTooLargeForJgit);
+ }
+
+ byte[] buffer = new byte[(int) len];
+ IO.readFully(fd, buffer, 0, buffer.length);
+
+ switch (chunkId) {
+ case MIDX_CHUNKID_OIDFANOUT:
+ builder.addOidFanout(buffer);
+ break;
+ case MIDX_CHUNKID_OIDLOOKUP:
+ builder.addOidLookUp(buffer);
+ break;
+ case MIDX_CHUNKID_PACKNAMES:
+ builder.addPackNames(buffer);
+ break;
+ case MIDX_CHUNKID_BITMAPPEDPACKS:
+ builder.addBitmappedPacks(buffer);
+ break;
+ case MIDX_CHUNKID_OBJECTOFFSETS:
+ builder.addObjectOffsets(buffer);
+ break;
+ case MIDX_CHUNKID_LARGEOFFSETS:
+ builder.addObjectLargeOffsets(buffer);
+ break;
+ default:
+ LOG.warn(MessageFormat.format(JGitText.get().midxChunkUnknown,
+ Integer.toHexString(chunkId)));
+ }
+ }
+ return builder.build();
+ }
+
+ private record ChunkSegment(int id, long offset) {}
+
+ /**
+ * Accumulate byte[] of the different chunks, to build a multipack index
+ */
+ // Visible for testing
+ static class MultiPackIndexBuilder {
+
+ private final int hashLength;
+
+ private int packCount;
+
+ private byte[] oidFanout;
+
+ private byte[] oidLookup;
+
+ private String[] packNames;
+
+ private byte[] bitmappedPackfiles;
+
+ private byte[] objectOffsets;
+
+ // Optional
+ private byte[] largeObjectOffsets;
+
+ // Optional
+ private byte[] bitmapPackOrder;
+
+ private MultiPackIndexBuilder(int hashLength) {
+ this.hashLength = hashLength;
+ }
+
+ /**
+ * Create builder
+ *
+ * @return A builder of {@link MultiPackIndex}.
+ */
+ static MultiPackIndexBuilder builder() {
+ return new MultiPackIndexBuilder(OBJECT_ID_LENGTH);
+ }
+
+ MultiPackIndexBuilder setPackCount(int packCount) {
+ this.packCount = packCount;
+ return this;
+ }
+
+ MultiPackIndexBuilder addOidFanout(byte[] buffer)
+ throws MultiPackIndexFormatException {
+ assertChunkNotSeenYet(oidFanout, MIDX_CHUNKID_OIDFANOUT);
+ oidFanout = buffer;
+ return this;
+ }
+
+ MultiPackIndexBuilder addOidLookUp(byte[] buffer)
+ throws MultiPackIndexFormatException {
+ assertChunkNotSeenYet(oidLookup, MIDX_CHUNKID_OIDLOOKUP);
+ oidLookup = buffer;
+ return this;
+ }
+
+ MultiPackIndexBuilder addPackNames(byte[] buffer)
+ throws MultiPackIndexFormatException {
+ assertChunkNotSeenYet(packNames, MIDX_CHUNKID_PACKNAMES);
+ packNames = new String(buffer, UTF_8).split("\u0000"); //$NON-NLS-1$
+ return this;
+ }
+
+ MultiPackIndexBuilder addBitmappedPacks(byte[] buffer)
+ throws MultiPackIndexFormatException {
+ assertChunkNotSeenYet(bitmappedPackfiles,
+ MIDX_CHUNKID_BITMAPPEDPACKS);
+ bitmappedPackfiles = buffer;
+ return this;
+ }
+
+ MultiPackIndexBuilder addObjectOffsets(byte[] buffer)
+ throws MultiPackIndexFormatException {
+ assertChunkNotSeenYet(objectOffsets, MIDX_CHUNKID_OBJECTOFFSETS);
+ objectOffsets = buffer;
+ return this;
+ }
+
+ MultiPackIndexBuilder addObjectLargeOffsets(byte[] buffer)
+ throws MultiPackIndexFormatException {
+ assertChunkNotSeenYet(largeObjectOffsets,
+ MIDX_CHUNKID_LARGEOFFSETS);
+ largeObjectOffsets = buffer;
+ return this;
+ }
+
+ MultiPackIndexBuilder addReverseIndex(byte[] buffer)
+ throws MultiPackIndexFormatException {
+ assertChunkNotSeenYet(bitmapPackOrder, MIDX_CHUNKID_REVINDEX);
+ bitmapPackOrder = buffer;
+ return this;
+ }
+
+ MultiPackIndex build() throws MultiPackIndexFormatException {
+ assertChunkNotNull(oidFanout, MIDX_CHUNKID_OIDFANOUT);
+ assertChunkNotNull(oidLookup, MIDX_CHUNKID_OIDLOOKUP);
+ assertChunkNotNull(packNames, MIDX_CHUNKID_PACKNAMES);
+ assertChunkNotNull(objectOffsets, MIDX_CHUNKID_OBJECTOFFSETS);
+
+ assertPackCounts(packCount, packNames.length);
+ return new MultiPackIndexV1(hashLength, oidFanout, oidLookup,
+ packNames, bitmappedPackfiles, objectOffsets, largeObjectOffsets);
+ }
+
+ private static void assertChunkNotNull(Object object, int chunkId)
+ throws MultiPackIndexFormatException {
+ if (object == null) {
+ throw new MultiPackIndexFormatException(
+ MessageFormat.format(JGitText.get().midxChunkNeeded,
+ Integer.toHexString(chunkId)));
+ }
+ }
+
+ private static void assertChunkNotSeenYet(Object object, int chunkId)
+ throws MultiPackIndexFormatException {
+ if (object != null) {
+ throw new MultiPackIndexFormatException(
+ MessageFormat.format(JGitText.get().midxChunkRepeated,
+ Integer.toHexString(chunkId)));
+ }
+ }
+
+ private static void assertPackCounts(int headerCount,
+ int packfileNamesCount) throws MultiPackIndexFormatException {
+ if (headerCount != packfileNamesCount) {
+ throw new MultiPackIndexFormatException(MessageFormat.format(
+ JGitText.get().multiPackIndexPackCountMismatch,
+ headerCount, packfileNamesCount));
+ }
+ }
+ }
+
+ /**
+ * Thrown when a MultiPackIndex file's format is different from we expected
+ */
+ public static class MultiPackIndexFormatException extends IOException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Construct an exception.
+ *
+ * @param why
+ * description of the type of error.
+ */
+ MultiPackIndexFormatException(String why) {
+ super(why);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexPrettyPrinter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexPrettyPrinter.java
new file mode 100644
index 0000000000..948b7bc174
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexPrettyPrinter.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2025, Google LLC
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.CHUNK_LOOKUP_WIDTH;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.util.NB;
+
+/**
+ * Prints a multipack index file in a human-readable format.
+ *
+ * @since 7.2
+ */
+@SuppressWarnings({ "boxing", "nls" })
+public class MultiPackIndexPrettyPrinter {
+
+ /**
+ * Writes to out, in human-readable format, the multipack index in rawMidx
+ *
+ * @param rawMidx the bytes of a multipack index
+ * @param out a writer
+ */
+ public static void prettyPrint(byte[] rawMidx, PrintWriter out) {
+ // Header (12 bytes)
+ out.println("[ 0] Magic: " + new String(rawMidx, 0, 4, UTF_8));
+ out.println("[ 4] Version number: " + (int) rawMidx[4]);
+ out.println("[ 5] OID version: " + (int) rawMidx[5]);
+ int chunkCount = rawMidx[6];
+ out.println("[ 6] # of chunks: " + chunkCount);
+ out.println("[ 7] # of bases: " + (int) rawMidx[7]);
+ int numberOfPacks = NB.decodeInt32(rawMidx, 8);
+ out.println("[ 8] # of packs: " + numberOfPacks);
+
+ // Chunk lookup table
+ List<ChunkSegment> chunkSegments = new ArrayList<>();
+ int current = printChunkLookup(out, rawMidx, chunkCount, chunkSegments);
+
+ for (int i = 0; i < chunkSegments.size() - 1; i++) {
+ ChunkSegment segment = chunkSegments.get(i);
+ if (current != segment.startOffset()) {
+ throw new IllegalStateException(String.format(
+ "We are at byte %d, but segment should start at %d",
+ current, segment.startOffset()));
+ }
+ out.printf("Starting chunk: %s @ %d%n", segment.chunkName(),
+ segment.startOffset());
+ switch (segment.chunkName()) {
+ case "OIDF" -> current = printOIDF(out, rawMidx, current);
+ case "OIDL" -> current = printOIDL(out, rawMidx, current,
+ chunkSegments.get(i + 1).startOffset);
+ case "OOFF" -> current = printOOFF(out, rawMidx, current,
+ chunkSegments.get(i + 1).startOffset);
+ case "PNAM" -> current = printPNAM(out, rawMidx, current,
+ chunkSegments.get(i + 1).startOffset);
+ case "RIDX" -> current = printRIDX(out, rawMidx, current,
+ chunkSegments.get(i + 1).startOffset);
+ default -> {
+ out.printf(
+ "Skipping %s (don't know how to print it yet)%n",
+ segment.chunkName());
+ current = (int) chunkSegments.get(i + 1).startOffset();
+ }
+ }
+ }
+ // Checksum is a SHA-1, use ObjectId to parse it
+ out.printf("[ %d] Checksum %s%n", current,
+ ObjectId.fromRaw(rawMidx, current).name());
+ out.printf("Total size: " + (current + 20));
+ }
+
+ private static int printChunkLookup(PrintWriter out, byte[] rawMidx, int chunkCount,
+ List<ChunkSegment> chunkSegments) {
+ out.println("Starting chunk lookup @ 12");
+ int current = 12;
+ for (int i = 0; i < chunkCount; i++) {
+ String chunkName = new String(rawMidx, current, 4, UTF_8);
+ long offset = NB.decodeInt64(rawMidx, current + 4);
+ out.printf("[ %d] |%8s|%8d|%n", current, chunkName, offset);
+ current += CHUNK_LOOKUP_WIDTH;
+ chunkSegments.add(new ChunkSegment(chunkName, offset));
+ }
+ String chunkName = "0000";
+ long offset = NB.decodeInt64(rawMidx, current + 4);
+ out.printf("[ %d] |%8s|%8d|%n", current, chunkName, offset);
+ current += CHUNK_LOOKUP_WIDTH;
+ chunkSegments.add(new ChunkSegment(chunkName, offset));
+ return current;
+ }
+
+ private static int printOIDF(PrintWriter out, byte[] rawMidx, int start) {
+ int current = start;
+ for (short i = 0; i < 256; i++) {
+ out.printf("[ %d] (%02X) %d%n", current, i,
+ NB.decodeInt32(rawMidx, current));
+ current += 4;
+ }
+ return current;
+ }
+
+ private static int printOIDL(PrintWriter out, byte[] rawMidx, int start, long end) {
+ int i = start;
+ while (i < end) {
+ out.printf("[ %d] %s%n", i,
+ ObjectId.fromRaw(rawMidx, i).name());
+ i += 20;
+ }
+ return i;
+ }
+
+ private static int printOOFF(PrintWriter out, byte[] rawMidx, int start, long end) {
+ int i = start;
+ while (i < end) {
+ out.printf("[ %d] %d %d%n", i, NB.decodeInt32(rawMidx, i),
+ NB.decodeInt32(rawMidx, i + 4));
+ i += 8;
+ }
+ return i;
+ }
+
+ private static int printRIDX(PrintWriter out, byte[] rawMidx, int start, long end) {
+ int i = start;
+ while (i < end) {
+ out.printf("[ %d] %d%n", i, NB.decodeInt32(rawMidx, i));
+ i += 4;
+ }
+ return (int) end;
+ }
+
+ private static int printPNAM(PrintWriter out, byte[] rawMidx, int start, long end) {
+ int nameStart = start;
+ for (int i = start; i < end; i++) {
+ if (rawMidx[i] == 0) {
+ out
+ .println(new String(rawMidx, nameStart, i - nameStart));
+ nameStart = i + 1;
+ }
+ }
+ return (int) end;
+ }
+
+ private record ChunkSegment(String chunkName, long startOffset) {
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexV1.java
new file mode 100644
index 0000000000..be752cc4b5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexV1.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2024, GerritForge Inc. and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.midx;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Set;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndexLoader.MultiPackIndexFormatException;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.util.NB;
+
+/**
+ * Support for the MultiPackIndex v1 format.
+ *
+ * @see MultiPackIndex
+ */
+class MultiPackIndexV1 implements MultiPackIndex {
+
+ private final OidLookup idx;
+
+ private final String[] packNames;
+
+ private final byte[] bitmappedPackfiles;
+
+ private final OffsetLookup offsets;
+
+ private final PackOffset result = new PackOffset();
+
+ MultiPackIndexV1(int hashLength, @NonNull byte[] oidFanout,
+ @NonNull byte[] oidLookup, String[] packNames,
+ byte[] bitmappedPackfiles, byte[] objectOffsets,
+ byte[] largeObjectOffsets) throws MultiPackIndexFormatException {
+ this.bitmappedPackfiles = bitmappedPackfiles;
+ this.idx = new OidLookup(hashLength, oidFanout, oidLookup);
+ this.offsets = new OffsetLookup(objectOffsets, largeObjectOffsets);
+ this.packNames = packNames;
+ }
+
+ @Override
+ public String[] getPackNames() {
+ return packNames;
+ }
+
+ @Override
+ public boolean hasObject(AnyObjectId oid) {
+ return idx.findMultiPackIndexPosition(oid) != -1;
+ }
+
+ @Override
+ @Nullable
+ public PackOffset find(AnyObjectId objectId) {
+ int position = idx.findMultiPackIndexPosition(objectId);
+ if (position == -1) {
+ return null;
+ }
+ offsets.getObjectOffset(position, result);
+ return result;
+ }
+
+ @Override
+ public void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
+ int matchLimit) {
+ idx.resolve(matches, id, matchLimit);
+ }
+
+ @Override
+ public long getMemorySize() {
+ int packNamesSize = Arrays.stream(packNames)
+ .mapToInt(s -> s.getBytes(StandardCharsets.UTF_8).length).sum();
+ return packNamesSize + byteArrayLengh(bitmappedPackfiles)
+ + idx.getMemorySize() + offsets.getMemorySize();
+ }
+
+ @Override
+ public String toString() {
+ return "MultiPackIndexV1 {idx=" + idx + ", packfileNames=" //$NON-NLS-1$ //$NON-NLS-2$
+ + Arrays.toString(packNames) + ", bitmappedPackfiles=" //$NON-NLS-1$
+ + byteArrayToString(bitmappedPackfiles) + ", objectOffsets=" //$NON-NLS-1$
+ + offsets + '}';
+ }
+
+ private static String byteArrayToString(byte[] array) {
+ return array == null ? "null" : new String(array); //$NON-NLS-1$
+ }
+
+ private static int byteArrayLengh(byte[] array) {
+ return array == null ? 0 : array.length;
+ }
+
+ /**
+ * Wraps the small and large offset chunks (if exists), to lookup offsets.
+ */
+ private static class OffsetLookup {
+ private static final int OBJECT_OFFSETS_DATA_WIDTH = 8;
+
+ private static final int BIT_31_ON = 0x80000000;
+
+ private static final int TOGGLE_BIT_31 = 0x7fff_ffff;
+
+ private final byte[] offsets;
+
+ private final byte[] largeOffsets;
+
+ /**
+ * Initialize the ObjectOffsets.
+ *
+ * @param offsets
+ * content of ObjectOffset Chunk.
+ * @param largeOffsets
+ * content of largo offsets chunks (can be null).
+ */
+ OffsetLookup(@NonNull byte[] offsets, byte[] largeOffsets) {
+ this.offsets = offsets;
+ this.largeOffsets = largeOffsets;
+ }
+
+ /**
+ * Get the metadata of a commit。
+ *
+ * @param position
+ * the position in the multi-pack-index of the object.
+ * @param result
+ * an instance of PackOffset to populate with the result.
+ */
+ void getObjectOffset(int position, PackOffset result) {
+ int offsetInChunk = position * OBJECT_OFFSETS_DATA_WIDTH;
+ int packId = NB.decodeInt32(offsets, offsetInChunk);
+ int offset = NB.decodeInt32(offsets, offsetInChunk + 4);
+ if ((offset & BIT_31_ON) != 0) {
+ long bigOffset;
+ if (largeOffsets == null) {
+ bigOffset = NB.decodeUInt32(offsets, offsetInChunk + 4);
+ } else {
+ int bigOffsetPos = (offset & TOGGLE_BIT_31);
+ bigOffset = NB.decodeInt64(largeOffsets, bigOffsetPos * 8);
+ }
+ result.setValues(packId, bigOffset);
+ return;
+ }
+ result.setValues(packId, offset);
+ }
+
+ long getMemorySize() {
+ return (long) byteArrayLengh(offsets)
+ + byteArrayLengh(largeOffsets);
+ }
+ }
+
+ /**
+ * Combines the fanout and oid list chunks, to lookup Oids with an efficient
+ * binary search
+ */
+ private static class OidLookup {
+
+ private static final int FANOUT = 256;
+
+ private final int hashLength;
+
+ private final int[] fanoutTable;
+
+ private final byte[] oidLookup;
+
+ /**
+ * Initialize the MultiPackIndexIndex.
+ *
+ * @param hashLength
+ * length of object hash.
+ * @param oidFanout
+ * content of OID Fanout Chunk.
+ * @param oidLookup
+ * content of OID Lookup Chunk.
+ * @throws MultiPackIndexFormatException
+ * MultiPackIndex file's format is different from we
+ * expected.
+ */
+ OidLookup(int hashLength, @NonNull byte[] oidFanout,
+ @NonNull byte[] oidLookup)
+ throws MultiPackIndexFormatException {
+ this.hashLength = hashLength;
+ this.oidLookup = oidLookup;
+
+ int[] table = new int[FANOUT];
+ long uint32;
+ for (int k = 0; k < table.length; k++) {
+ uint32 = NB.decodeUInt32(oidFanout, k * 4);
+ if (uint32 > Integer.MAX_VALUE) {
+ throw new MultiPackIndexFormatException(
+ JGitText.get().multiPackIndexFileIsTooLargeForJgit);
+ }
+ table[k] = (int) uint32;
+ }
+ this.fanoutTable = table;
+ }
+
+ /**
+ * Find the position in the MultiPackIndex file of the specified id.
+ *
+ * @param id
+ * the id for which the multi-pack-index position will be
+ * found.
+ * @return the MultiPackIndex position or -1 if the object was not
+ * found.
+ */
+ int findMultiPackIndexPosition(AnyObjectId id) {
+ int levelOne = id.getFirstByte();
+ int high = fanoutTable[levelOne];
+ int low = 0;
+ if (levelOne > 0) {
+ low = fanoutTable[levelOne - 1];
+ }
+ while (low < high) {
+ int mid = (low + high) >>> 1;
+ int cmp = id.compareTo(oidLookup, hashLength * mid);
+ if (cmp < 0) {
+ high = mid;
+ } else if (cmp == 0) {
+ return mid;
+ } else {
+ low = mid + 1;
+ }
+ }
+ return -1;
+ }
+
+ void resolve(Set<ObjectId> matches, AbbreviatedObjectId id,
+ int matchLimit) {
+ if (matches.size() >= matchLimit) {
+ return;
+ }
+
+ if (oidLookup.length == 0) {
+ return;
+ }
+
+ int high = fanoutTable[id.getFirstByte()];
+ int low = id.getFirstByte() == 0 ? 0
+ : fanoutTable[id.getFirstByte() - 1];
+ do {
+ int p = (low + high) >>> 1;
+ int cmp = id.prefixCompare(oidLookup, idOffset(p));
+ if (cmp < 0) {
+ high = p;
+ continue;
+ }
+
+ if (cmp > 0) {
+ low = p + 1;
+ continue;
+ }
+
+ // Got a match.
+ // We may have landed in the middle of the matches. Move
+ // backwards to the start of matches, then walk forwards.
+ while (0 < p
+ && id.prefixCompare(oidLookup, idOffset(p - 1)) == 0) {
+ p--;
+ }
+ while (p < high && id.prefixCompare(oidLookup, idOffset(p)) == 0
+ && matches.size() < matchLimit) {
+ matches.add(ObjectId.fromRaw(oidLookup, idOffset(p)));
+ p++;
+ }
+ return;
+ } while (low < high);
+ }
+
+ private int idOffset(int position) {
+ return position * hashLength;
+ }
+
+ long getMemorySize() {
+ return 4L + byteArrayLengh(oidLookup) + (FANOUT * 4);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriter.java
new file mode 100644
index 0000000000..b42c821a44
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriter.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2025, Google LLC
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.CHUNK_LOOKUP_WIDTH;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_LARGEOFFSETS;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OBJECTOFFSETS;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OIDFANOUT;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_OIDLOOKUP;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_PACKNAMES;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_CHUNKID_REVINDEX;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_SIGNATURE;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MIDX_VERSION;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.MULTIPACK_INDEX_FANOUT_SIZE;
+import static org.eclipse.jgit.internal.storage.midx.MultiPackIndexConstants.OID_HASH_VERSION;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.internal.storage.io.CancellableDigestOutputStream;
+import org.eclipse.jgit.internal.storage.midx.PackIndexMerger.MidxMutableEntry;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.util.NB;
+
+/**
+ * Writes a collection of indexes as a multipack index.
+ * <p>
+ * See <a href=
+ * "https://git-scm.com/docs/pack-format#_multi_pack_index_midx_files_have_the_following_format">multipack
+ * index format spec</a>
+ *
+ * @since 7.2
+ */
+public class MultiPackIndexWriter {
+
+ private static final int LIMIT_31_BITS = (1 << 31) - 1;
+
+ private static final int MIDX_HEADER_SIZE = 12;
+
+ /**
+ * Writes the inputs in the multipack index format in the outputStream.
+ *
+ * @param monitor
+ * progress monitor
+ * @param outputStream
+ * stream to write the multipack index file
+ * @param inputs
+ * pairs of name and index for each pack to include in the
+ * multipack index.
+ * @return bytes written into the stream
+ * @throws IOException
+ * Error writing to the stream
+ */
+ public long write(ProgressMonitor monitor, OutputStream outputStream,
+ Map<String, PackIndex> inputs) throws IOException {
+ PackIndexMerger data = new PackIndexMerger(inputs);
+
+ // List of chunks in the order they need to be written
+ List<ChunkHeader> chunkHeaders = createChunkHeaders(data);
+ long expectedSize = calculateExpectedSize(chunkHeaders);
+ try (CancellableDigestOutputStream out = new CancellableDigestOutputStream(
+ monitor, outputStream)) {
+ writeHeader(out, chunkHeaders.size(), data.getPackCount());
+ writeChunkLookup(out, chunkHeaders);
+
+ WriteContext ctx = new WriteContext(out, data);
+ for (ChunkHeader chunk : chunkHeaders) {
+ chunk.writerFn.write(ctx);
+ }
+ writeCheckSum(out);
+ if (expectedSize != out.length()) {
+ throw new IllegalStateException(String.format(
+ JGitText.get().multiPackIndexUnexpectedSize,
+ Long.valueOf(expectedSize),
+ Long.valueOf(out.length())));
+ }
+ return expectedSize;
+ } catch (InterruptedIOException e) {
+ throw new IOException(JGitText.get().multiPackIndexWritingCancelled,
+ e);
+ }
+ }
+
+ private static long calculateExpectedSize(List<ChunkHeader> chunks) {
+ int chunkLookup = (chunks.size() + 1) * CHUNK_LOOKUP_WIDTH;
+ long chunkContent = chunks.stream().mapToLong(c -> c.size).sum();
+ return /* header */ 12 + chunkLookup + chunkContent + /* CRC */ 20;
+ }
+
+ private List<ChunkHeader> createChunkHeaders(PackIndexMerger data) {
+ List<ChunkHeader> chunkHeaders = new ArrayList<>();
+ chunkHeaders.add(new ChunkHeader(MIDX_CHUNKID_OIDFANOUT,
+ MULTIPACK_INDEX_FANOUT_SIZE, this::writeFanoutTable));
+ chunkHeaders.add(new ChunkHeader(MIDX_CHUNKID_OIDLOOKUP,
+ (long) data.getUniqueObjectCount() * OBJECT_ID_LENGTH,
+ this::writeOidLookUp));
+ chunkHeaders.add(new ChunkHeader(MIDX_CHUNKID_OBJECTOFFSETS,
+ 8L * data.getUniqueObjectCount(), this::writeObjectOffsets));
+ if (data.needsLargeOffsetsChunk()) {
+ chunkHeaders.add(new ChunkHeader(MIDX_CHUNKID_LARGEOFFSETS,
+ 8L * data.getOffsetsOver31BitsCount(),
+ this::writeObjectLargeOffsets));
+ }
+ chunkHeaders.add(new ChunkHeader(MIDX_CHUNKID_REVINDEX,
+ 4L * data.getUniqueObjectCount(), this::writeRidx));
+
+ int packNamesSize = data.getPackNames().stream()
+ .mapToInt(String::length).map(i -> i + 1 /* null at the end */)
+ .sum();
+ chunkHeaders.add(new ChunkHeader(MIDX_CHUNKID_PACKNAMES, packNamesSize,
+ this::writePackfileNames));
+ return chunkHeaders;
+ }
+
+ /**
+ * Write the first 12 bytes of the multipack index.
+ * <p>
+ * These bytes include things like magic number, version, number of
+ * chunks...
+ *
+ * @param out
+ * output stream to write
+ * @param numChunks
+ * number of chunks this multipack index is going to have
+ * @param packCount
+ * number of packs covered by this multipack index
+ * @throws IOException
+ * error writing to the output stream
+ */
+ private void writeHeader(CancellableDigestOutputStream out, int numChunks,
+ int packCount) throws IOException {
+ byte[] headerBuffer = new byte[MIDX_HEADER_SIZE];
+ NB.encodeInt32(headerBuffer, 0, MIDX_SIGNATURE);
+ byte[] buff = { MIDX_VERSION, OID_HASH_VERSION, (byte) numChunks,
+ (byte) 0 };
+ System.arraycopy(buff, 0, headerBuffer, 4, 4);
+ NB.encodeInt32(headerBuffer, 8, packCount);
+ out.write(headerBuffer, 0, headerBuffer.length);
+ out.flush();
+ }
+
+ /**
+ * Write a table of "chunkId, start-offset", with a special value "0,
+ * end-of-previous_chunk", to mark the end.
+ *
+ * @param out
+ * output stream to write
+ * @param chunkHeaders
+ * list of chunks in the order they are expected to be written
+ * @throws IOException
+ * error writing to the output stream
+ */
+ private void writeChunkLookup(CancellableDigestOutputStream out,
+ List<ChunkHeader> chunkHeaders) throws IOException {
+
+ // first chunk will start at header + this lookup block
+ long chunkStart = MIDX_HEADER_SIZE
+ + (long) (chunkHeaders.size() + 1) * CHUNK_LOOKUP_WIDTH;
+ byte[] chunkEntry = new byte[CHUNK_LOOKUP_WIDTH];
+ for (ChunkHeader chunkHeader : chunkHeaders) {
+ NB.encodeInt32(chunkEntry, 0, chunkHeader.chunkId);
+ NB.encodeInt64(chunkEntry, 4, chunkStart);
+ out.write(chunkEntry);
+ chunkStart += chunkHeader.size;
+ }
+ // Terminating label for the block
+ // (chunkid 0, offset where the next block would start)
+ NB.encodeInt32(chunkEntry, 0, 0);
+ NB.encodeInt64(chunkEntry, 4, chunkStart);
+ out.write(chunkEntry);
+ }
+
+ /**
+ * Write the fanout table for the object ids
+ * <p>
+ * Table with 256 entries (one byte), where the ith entry, F[i], stores the
+ * number of OIDs with first byte at most i. Thus, F[255] stores the total
+ * number of objects.
+ *
+ * @param ctx
+ * write context
+ * @throws IOException
+ * error writing to the output stream
+ */
+
+ private void writeFanoutTable(WriteContext ctx) throws IOException {
+ byte[] tmp = new byte[4];
+ int[] fanout = new int[256];
+ Iterator<MidxMutableEntry> iterator = ctx.data.bySha1Iterator();
+ while (iterator.hasNext()) {
+ MidxMutableEntry e = iterator.next();
+ fanout[e.getObjectId().getFirstByte() & 0xff]++;
+ }
+ for (int i = 1; i < fanout.length; i++) {
+ fanout[i] += fanout[i - 1];
+ }
+ for (int n : fanout) {
+ NB.encodeInt32(tmp, 0, n);
+ ctx.out.write(tmp, 0, 4);
+ }
+ }
+
+ /**
+ * Write the OID lookup chunk
+ * <p>
+ * A list of OIDs in sha1 order.
+ *
+ * @param ctx
+ * write context
+ * @throws IOException
+ * error writing to the output stream
+ */
+ private void writeOidLookUp(WriteContext ctx) throws IOException {
+ byte[] tmp = new byte[OBJECT_ID_LENGTH];
+
+ Iterator<MidxMutableEntry> iterator = ctx.data.bySha1Iterator();
+ while (iterator.hasNext()) {
+ MidxMutableEntry e = iterator.next();
+ e.getObjectId().copyRawTo(tmp, 0);
+ ctx.out.write(tmp, 0, OBJECT_ID_LENGTH);
+ }
+ }
+
+ /**
+ * Write the object offsets chunk
+ * <p>
+ * A list of offsets, parallel to the list of OIDs. If the offset is too
+ * large (see {@link #fitsIn31bits(long)}), this contains the position in
+ * the large offsets list (marked with a 1 in the most significant bit).
+ *
+ * @param ctx
+ * write context
+ * @throws IOException
+ * error writing to the output stream
+ */
+ private void writeObjectOffsets(WriteContext ctx) throws IOException {
+ byte[] entry = new byte[8];
+ Iterator<MidxMutableEntry> iterator = ctx.data.bySha1Iterator();
+ while (iterator.hasNext()) {
+ MidxMutableEntry e = iterator.next();
+ NB.encodeInt32(entry, 0, e.getPackId());
+ if (!ctx.data.needsLargeOffsetsChunk()
+ || fitsIn31bits(e.getOffset())) {
+ NB.encodeInt32(entry, 4, (int) e.getOffset());
+ } else {
+ int offloadedPosition = ctx.largeOffsets.append(e.getOffset());
+ NB.encodeInt32(entry, 4, offloadedPosition | (1 << 31));
+ }
+ ctx.out.write(entry);
+ }
+ }
+
+ /**
+ * Writes the reverse index chunk
+ * <p>
+ * This stores the position of the objects in the main index, ordered first
+ * by pack and then by offset
+ *
+ * @param ctx
+ * write context
+ * @throws IOException
+ * erorr writing to the output stream
+ */
+ private void writeRidx(WriteContext ctx) throws IOException {
+ Map<Integer, List<OffsetPosition>> packOffsets = new HashMap<>(
+ ctx.data.getPackCount());
+ // TODO(ifrade): Brute force solution loading all offsets/packs in
+ // memory. We could also iterate reverse indexes looking up
+ // their position in the midx (and discarding if the pack doesn't
+ // match).
+ Iterator<MidxMutableEntry> iterator = ctx.data.bySha1Iterator();
+ int midxPosition = 0;
+ while (iterator.hasNext()) {
+ MidxMutableEntry e = iterator.next();
+ OffsetPosition op = new OffsetPosition(e.getOffset(), midxPosition);
+ midxPosition++;
+ packOffsets.computeIfAbsent(Integer.valueOf(e.getPackId()),
+ k -> new ArrayList<>()).add(op);
+ }
+
+ for (int i = 0; i < ctx.data.getPackCount(); i++) {
+ List<OffsetPosition> offsetsForPack = packOffsets
+ .get(Integer.valueOf(i));
+ if (offsetsForPack == null) {
+ continue;
+ }
+ offsetsForPack.sort(Comparator.comparing(OffsetPosition::offset));
+ byte[] ridxForPack = new byte[4 * offsetsForPack.size()];
+ for (int j = 0; j < offsetsForPack.size(); j++) {
+ NB.encodeInt32(ridxForPack, j * 4,
+ offsetsForPack.get(j).position);
+ }
+ ctx.out.write(ridxForPack);
+ }
+ }
+
+ /**
+ * Write the large offset chunk
+ * <p>
+ * A list of large offsets (long). The regular offset chunk will point to a
+ * position here.
+ *
+ * @param ctx
+ * writer context
+ * @throws IOException
+ * error writing to the output stream
+ */
+ private void writeObjectLargeOffsets(WriteContext ctx) throws IOException {
+ ctx.out.write(ctx.largeOffsets.offsets, 0,
+ ctx.largeOffsets.bytePosition);
+ }
+
+ /**
+ * Write the list of packfiles chunk
+ * <p>
+ * List of packfiles (in lexicographical order) with an \0 at the end
+ *
+ * @param ctx
+ * writer context
+ * @throws IOException
+ * error writing to the output stream
+ */
+ private void writePackfileNames(WriteContext ctx) throws IOException {
+ for (String packName : ctx.data.getPackNames()) {
+ // Spec doesn't talk about encoding.
+ ctx.out.write(packName.getBytes(StandardCharsets.UTF_8));
+ ctx.out.write(0);
+ }
+ }
+
+ /**
+ * Write final checksum of the data written to the stream
+ *
+ * @param out
+ * output stream used to write
+ * @throws IOException
+ * error writing to the output stream
+ */
+ private void writeCheckSum(CancellableDigestOutputStream out)
+ throws IOException {
+ out.write(out.getDigest());
+ out.flush();
+ }
+
+ private record OffsetPosition(long offset, int position) {
+ }
+
+ /**
+ * If there is at least one offset value larger than 2^32-1, then the large
+ * offset chunk must exist, and offsets larger than 2^31-1 must be stored in
+ * it instead
+ *
+ * @param offset
+ * object offset
+ *
+ * @return true if the offset fits in 31 bits
+ */
+ private static boolean fitsIn31bits(long offset) {
+ return offset <= LIMIT_31_BITS;
+ }
+
+ private static class LargeOffsets {
+ private final byte[] offsets;
+
+ private int bytePosition;
+
+ LargeOffsets(int largeOffsetsCount) {
+ offsets = new byte[largeOffsetsCount * 8];
+ bytePosition = 0;
+ }
+
+ /**
+ * Add an offset to the large offset chunk
+ *
+ * @param largeOffset
+ * a large offset
+ * @return the position of the just inserted offset (as in number of
+ * offsets, NOT in bytes)
+ */
+ int append(long largeOffset) {
+ int at = bytePosition;
+ NB.encodeInt64(offsets, at, largeOffset);
+ bytePosition += 8;
+ return at / 8;
+ }
+ }
+
+ private record ChunkHeader(int chunkId, long size, ChunkWriter writerFn) {
+ }
+
+ @FunctionalInterface
+ private interface ChunkWriter {
+ void write(WriteContext ctx) throws IOException;
+ }
+
+ private static class WriteContext {
+ final CancellableDigestOutputStream out;
+
+ final PackIndexMerger data;
+
+ final LargeOffsets largeOffsets;
+
+ WriteContext(CancellableDigestOutputStream out, PackIndexMerger data) {
+ this.out = out;
+ this.data = data;
+ this.largeOffsets = new LargeOffsets(
+ data.getOffsetsOver31BitsCount());
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/PackIndexMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/PackIndexMerger.java
new file mode 100644
index 0000000000..f23665849e
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/PackIndexMerger.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2025, Google LLC
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.stream.Collectors;
+
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.MutableObjectId;
+
+/**
+ * Collect the stats and offers an iterator over the union of n-pack indexes.
+ * <p>
+ * The multipack index is a list of (sha1, packid, offset) ordered by sha1. We
+ * can build it from the individual pack indexes (sha1, offset) ordered by sha1,
+ * with a simple merge ignoring duplicates.
+ * <p>
+ * This class encapsulates the merging logic and precalculates the stats that
+ * the index needs (like total count of objects). To limit memory consumption,
+ * it does the merge as it goes during the iteration and iterators use mutable
+ * entries. The stats of the combined index are calculated in an iteration at
+ * construction time.
+ */
+class PackIndexMerger {
+
+ private static final int LIMIT_31_BITS = (1 << 31) - 1;
+
+ private static final long LIMIT_32_BITS = (1L << 32) - 1;
+
+ /**
+ * Object returned by the iterator.
+ * <p>
+ * The iterator returns (on each next()) the same instance with different
+ * values, to avoid allocating many short-lived objects. Callers should not
+ * keep a reference to that returned value.
+ */
+ static class MidxMutableEntry {
+ // The object id
+ private final MutableObjectId oid = new MutableObjectId();
+
+ // Position of the pack in the ordered list of pack in this merger
+ private int packId;
+
+ // Offset in its pack
+ private long offset;
+
+ public AnyObjectId getObjectId() {
+ return oid;
+ }
+
+ public int getPackId() {
+ return packId;
+ }
+
+ public long getOffset() {
+ return offset;
+ }
+
+ /**
+ * Copy values from another mutable entry
+ *
+ * @param packId
+ * packId
+ * @param other
+ * another mutable entry
+ */
+ private void fill(int packId, PackIndex.MutableEntry other) {
+ other.copyOidTo(oid);
+ this.packId = packId;
+ this.offset = other.getOffset();
+ }
+ }
+
+ private final List<String> packNames;
+
+ private final List<PackIndex> indexes;
+
+ private final boolean needsLargeOffsetsChunk;
+
+ private final int offsetsOver31BitsCount;
+
+ private final int uniqueObjectCount;
+
+ PackIndexMerger(Map<String, PackIndex> packs) {
+ this.packNames = packs.keySet().stream().sorted()
+ .collect(Collectors.toUnmodifiableList());
+
+ this.indexes = packNames.stream().map(packs::get)
+ .collect(Collectors.toUnmodifiableList());
+
+ // Iterate for duplicates
+ int objectCount = 0;
+ boolean hasLargeOffsets = false;
+ int over31bits = 0;
+ MutableObjectId lastSeen = new MutableObjectId();
+ MultiIndexIterator it = new MultiIndexIterator(indexes);
+ while (it.hasNext()) {
+ MidxMutableEntry entry = it.next();
+ if (lastSeen.equals(entry.oid)) {
+ continue;
+ }
+ // If there is at least one offset value larger than 2^32-1, then
+ // the large offset chunk must exist, and offsets larger than
+ // 2^31-1 must be stored in it instead
+ if (entry.offset > LIMIT_32_BITS) {
+ hasLargeOffsets = true;
+ }
+ if (entry.offset > LIMIT_31_BITS) {
+ over31bits++;
+ }
+
+ lastSeen.fromObjectId(entry.oid);
+ objectCount++;
+ }
+ uniqueObjectCount = objectCount;
+ offsetsOver31BitsCount = over31bits;
+ needsLargeOffsetsChunk = hasLargeOffsets;
+ }
+
+ /**
+ * Object count of the merged index (i.e. without duplicates)
+ *
+ * @return object count of the merged index
+ */
+ int getUniqueObjectCount() {
+ return uniqueObjectCount;
+ }
+
+ /**
+ * If any object in any of the indexes has an offset over 2^32-1
+ *
+ * @return true if there is any object with offset > 2^32 -1
+ */
+ boolean needsLargeOffsetsChunk() {
+ return needsLargeOffsetsChunk;
+ }
+
+ /**
+ * How many object have offsets over 2^31-1
+ * <p>
+ * Per multipack index spec, IF there is large offset chunk, all this
+ * offsets should be there.
+ *
+ * @return number of objects with offsets over 2^31-1
+ */
+ int getOffsetsOver31BitsCount() {
+ return offsetsOver31BitsCount;
+ }
+
+ /**
+ * List of pack names in alphabetical order.
+ * <p>
+ * Order matters: In case of duplicates, the multipack index prefers the
+ * first package with it. This is in the same order we are using to
+ * prioritize duplicates.
+ *
+ * @return List of pack names, in the order used by the merge.
+ */
+ List<String> getPackNames() {
+ return packNames;
+ }
+
+ /**
+ * How many packs are being merged
+ *
+ * @return count of packs merged
+ */
+ int getPackCount() {
+ return packNames.size();
+ }
+
+ /**
+ * Iterator over the merged indexes in sha1 order without duplicates
+ * <p>
+ * The returned entry in the iterator is mutable, callers should NOT keep a
+ * reference to it.
+ *
+ * @return an iterator in sha1 order without duplicates.
+ */
+ Iterator<MidxMutableEntry> bySha1Iterator() {
+ return new DedupMultiIndexIterator(new MultiIndexIterator(indexes),
+ getUniqueObjectCount());
+ }
+
+ /**
+ * For testing. Iterate all entries, not skipping duplicates (stable order)
+ *
+ * @return an iterator of all objects in sha1 order, including duplicates.
+ */
+ Iterator<MidxMutableEntry> rawIterator() {
+ return new MultiIndexIterator(indexes);
+ }
+
+ /**
+ * Iterator over n-indexes in ObjectId order.
+ * <p>
+ * It returns duplicates if the same object id is in different indexes. Wrap
+ * it with {@link DedupMultiIndexIterator (Iterator, int)} to avoid
+ * duplicates.
+ */
+ private static final class MultiIndexIterator
+ implements Iterator<MidxMutableEntry> {
+
+ private final List<PackIndexPeekIterator> indexIterators;
+
+ private final MidxMutableEntry mutableEntry = new MidxMutableEntry();
+
+ MultiIndexIterator(List<PackIndex> indexes) {
+ this.indexIterators = new ArrayList<>(indexes.size());
+ for (int i = 0; i < indexes.size(); i++) {
+ PackIndexPeekIterator it = new PackIndexPeekIterator(i,
+ indexes.get(i));
+ // Position in the first element
+ if (it.next() != null) {
+ indexIterators.add(it);
+ }
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return !indexIterators.isEmpty();
+ }
+
+ @Override
+ public MidxMutableEntry next() {
+ PackIndexPeekIterator winner = null;
+ for (int index = 0; index < indexIterators.size(); index++) {
+ PackIndexPeekIterator current = indexIterators.get(index);
+ if (winner == null
+ || current.peek().compareBySha1To(winner.peek()) < 0) {
+ winner = current;
+ }
+ }
+
+ if (winner == null) {
+ throw new NoSuchElementException();
+ }
+
+ mutableEntry.fill(winner.getPackId(), winner.peek());
+ if (winner.next() == null) {
+ indexIterators.remove(winner);
+ };
+ return mutableEntry;
+ }
+ }
+
+ private static class DedupMultiIndexIterator
+ implements Iterator<MidxMutableEntry> {
+ private final MultiIndexIterator src;
+
+ private int remaining;
+
+ private final MutableObjectId lastOid = new MutableObjectId();
+
+ DedupMultiIndexIterator(MultiIndexIterator src, int totalCount) {
+ this.src = src;
+ this.remaining = totalCount;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return remaining > 0;
+ }
+
+ @Override
+ public MidxMutableEntry next() {
+ MidxMutableEntry next = src.next();
+ while (next != null && lastOid.equals(next.oid)) {
+ next = src.next();
+ }
+
+ if (next == null) {
+ throw new NoSuchElementException();
+ }
+
+ lastOid.fromObjectId(next.oid);
+ remaining--;
+ return next;
+ }
+ }
+
+ /**
+ * Convenience around the PackIndex iterator to read the current value
+ * multiple times without consuming it.
+ * <p>
+ * This is used to merge indexes in the multipack index, where we need to
+ * compare the current value between indexes multiple times to find the
+ * next.
+ * <p>
+ * We could also implement this keeping the position (int) and
+ * MutableEntry#getObjectId, but that would create an ObjectId per entry.
+ * This implementation reuses the MutableEntry and avoid instantiations.
+ */
+ // Visible for testing
+ static class PackIndexPeekIterator {
+ private final Iterator<PackIndex.MutableEntry> it;
+
+ private final int packId;
+
+ PackIndex.MutableEntry current;
+
+ PackIndexPeekIterator(int packId, PackIndex index) {
+ it = index.iterator();
+ this.packId = packId;
+ }
+
+ PackIndex.MutableEntry next() {
+ if (it.hasNext()) {
+ current = it.next();
+ } else {
+ current = null;
+ }
+ return current;
+ }
+
+ PackIndex.MutableEntry peek() {
+ return current;
+ }
+
+ int getPackId() {
+ return packId;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/CachedPackUriProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/CachedPackUriProvider.java
index 0b419da467..32cd635edf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/CachedPackUriProvider.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/CachedPackUriProvider.java
@@ -21,14 +21,18 @@ import org.eclipse.jgit.annotations.Nullable;
public interface CachedPackUriProvider {
/**
- * @param pack the cached pack for which to check if a corresponding URI
- * exists
- * @param protocolsSupported the protocols that the client has declared
- * support for; if a URI is returned, it must be of one of these
- * protocols
- * @throws IOException implementations may throw this
- * @return if a URI corresponds to the cached pack, an object
- * containing the URI and some other information; null otherwise
+ * Get pack info
+ *
+ * @param pack
+ * the cached pack for which to check if a corresponding URI
+ * exists
+ * @param protocolsSupported
+ * the protocols that the client has declared support for; if a
+ * URI is returned, it must be of one of these protocols
+ * @throws IOException
+ * implementations may throw this
+ * @return if a URI corresponds to the cached pack, an object containing the
+ * URI and some other information; null otherwise
* @since 5.5
*/
@Nullable
@@ -62,6 +66,8 @@ public interface CachedPackUriProvider {
}
/**
+ * Get the hash of the packfile
+ *
* @return the hash of the packfile as a hexadecimal string
*/
public String getHash() {
@@ -69,6 +75,8 @@ public interface CachedPackUriProvider {
}
/**
+ * Get the URI corresponding to the packfile
+ *
* @return the URI corresponding to the packfile
*/
public String getUri() {
@@ -76,6 +84,8 @@ public interface CachedPackUriProvider {
}
/**
+ * Get the size of the packfile in bytes
+ *
* @return the size of the packfile in bytes (-1 if unknown)
*/
public long getSize() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndex.java
index 7ed5defbe4..0f71c7fe39 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaIndex.java
@@ -48,7 +48,7 @@ public class DeltaIndex {
* @return estimated size. Approximately {@code 1.75 * sourceLength}.
*/
public static long estimateIndexSize(int sourceLength) {
- return sourceLength + (sourceLength * 3 / 4);
+ return sourceLength + (sourceLength * 3L / 4);
}
/**
@@ -393,7 +393,6 @@ public class DeltaIndex {
return start - resPtr;
}
- /** {@inheritDoc} */
@Override
@SuppressWarnings("nls")
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java
index 132eb5095b..03d6f16733 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaTask.java
@@ -11,10 +11,10 @@
package org.eclipse.jgit.internal.storage.pack;
import java.io.IOException;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
-import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
@@ -232,14 +232,15 @@ final class DeltaTask implements Callable<Object> {
}
private final Block block;
- final LinkedList<Slice> slices;
+
+ final ArrayDeque<Slice> slices;
private ObjectReader or;
private DeltaWindow dw;
DeltaTask(Block b) {
this.block = b;
- this.slices = new LinkedList<>();
+ this.slices = new ArrayDeque<>();
}
void add(Slice s) {
@@ -254,7 +255,6 @@ final class DeltaTask implements Callable<Object> {
slices.add(s);
}
- /** {@inheritDoc} */
@Override
public Object call() throws Exception {
or = block.templateReader.newReader();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindowEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindowEntry.java
index 7f8504d6c0..b3f0574164 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindowEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/DeltaWindowEntry.java
@@ -27,22 +27,38 @@ final class DeltaWindowEntry {
this.buffer = null;
}
- /** @return current delta chain depth of this object. */
+ /**
+ * Get delta chain depth
+ *
+ * @return current delta chain depth of this object.
+ */
final int depth() {
return object.getDeltaDepth();
}
- /** @return type of the object in this window entry. */
+ /**
+ * Get object type in this window entry
+ *
+ * @return type of the object in this window entry.
+ */
final int type() {
return object.getType();
}
- /** @return estimated unpacked size of the object, in bytes . */
+ /**
+ * Get estimated unpacked object size
+ *
+ * @return estimated unpacked size of the object, in bytes .
+ */
final int size() {
return object.getWeight();
}
- /** @return true if there is no object stored in this entry. */
+ /**
+ * Whether the entry is empty
+ *
+ * @return true if there is no object stored in this entry.
+ */
final boolean empty() {
return object == null;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectToPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectToPack.java
index d9013bff26..ea5b9461ca 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectToPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectToPack.java
@@ -156,7 +156,6 @@ public class ObjectToPack extends PackedObjectInfo {
return 1 < getOffset(); // markWantWrite sets 1.
}
- /** {@inheritDoc} */
@Override
public final int getType() {
return (flags >> TYPE_SHIFT) & 0x7;
@@ -357,7 +356,6 @@ public class ObjectToPack extends PackedObjectInfo {
// Empty by default.
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackBitmapIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackBitmapIndexWriter.java
new file mode 100644
index 0000000000..9cf8c7f2b5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackBitmapIndexWriter.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024, Google Inc.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.pack;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder;
+
+/**
+ * Represents a function that accepts a collection of bitmaps and write them
+ * into storage.
+ */
+@FunctionalInterface
+public interface PackBitmapIndexWriter {
+ /**
+ * @param bitmaps
+ * list of bitmaps to be written to a bitmap index
+ * @param packChecksum
+ * checksum of the pack that the bitmap index refers to
+ * @throws IOException
+ * thrown in case of IO errors while writing the bitmap index
+ */
+ public void write(PackBitmapIndexBuilder bitmaps, byte[] packChecksum)
+ throws IOException;
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java
index adad411c6f..d5bb5f2e2f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java
@@ -36,7 +36,10 @@ public enum PackExt {
COMMIT_GRAPH("graph"), //$NON-NLS-1$
/** An object size index. */
- OBJECT_SIZE_INDEX("objsize"); //$NON-NLS-1$
+ OBJECT_SIZE_INDEX("objsize"), //$NON-NLS-1$
+
+ /** Multi pack index */
+ MULTI_PACK_INDEX("midx"); //$NON-NLS-1$
private final String ext;
@@ -71,7 +74,15 @@ public enum PackExt {
return 1 << getPosition();
}
- /** {@inheritDoc} */
+ /**
+ * Format a temporary file extension for this PackExt.
+ *
+ * @return a temporary file extension
+ */
+ public String getTmpExtension() {
+ return String.format(".%s_tmp", ext); //$NON-NLS-1$
+ }
+
@Override
public String toString() {
return String.format("PackExt[%s, bit=0x%s]", getExtension(), //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackIndexWriter.java
new file mode 100644
index 0000000000..f69e68d4ba
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackIndexWriter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024, Google LLC.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.storage.pack;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.jgit.transport.PackedObjectInfo;
+
+/**
+ * Represents a function that accepts a collection of objects to write into a
+ * primary pack index storage format.
+ */
+public interface PackIndexWriter {
+ /**
+ * Write all object entries to the index stream.
+ *
+ * @param toStore
+ * sorted list of objects to store in the index. The caller must
+ * have previously sorted the list using
+ * {@link org.eclipse.jgit.transport.PackedObjectInfo}'s native
+ * {@link java.lang.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 java.io.IOException
+ * an error occurred while writing to the output stream, or the
+ * underlying format cannot store the object data supplied.
+ */
+ void write(List<? extends PackedObjectInfo> toStore,
+ byte[] packDataChecksum) throws IOException;
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
index bad572459a..27fb814f64 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
@@ -58,10 +58,10 @@ import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.SearchForReuseTimeout;
import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.file.BasePackIndexWriter;
import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder;
-import org.eclipse.jgit.internal.storage.file.PackBitmapIndexWriterV1;
-import org.eclipse.jgit.internal.storage.file.PackIndexWriter;
import org.eclipse.jgit.internal.storage.file.PackObjectSizeIndexWriter;
+import org.eclipse.jgit.internal.storage.file.PackReverseIndexWriter;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.AsyncObjectSizeQueue;
import org.eclipse.jgit.lib.BatchingProgressMonitor;
@@ -118,9 +118,9 @@ import org.eclipse.jgit.util.TemporaryBuffer;
* {@link #preparePack(ProgressMonitor, Set, Set)}, and streaming with
* {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)}. If the
* pack is being stored as a file the matching index can be written out after
- * writing the pack by {@link #writeIndex(OutputStream)}. An optional bitmap
+ * writing the pack by {@link #writeIndex(PackIndexWriter)}. An optional bitmap
* index can be made by calling {@link #prepareBitmapIndex(ProgressMonitor)}
- * followed by {@link #writeBitmapIndex(OutputStream)}.
+ * followed by {@link #writeBitmapIndex(PackBitmapIndexWriter)}.
* </p>
* <p>
* Class provide set of configurable options and
@@ -145,44 +145,46 @@ public class PackWriter implements AutoCloseable {
private static final Map<WeakReference<PackWriter>, Boolean> instances =
new ConcurrentHashMap<>();
- private static final Iterable<PackWriter> instancesIterable = () -> new Iterator<>() {
+ private static Iterator<PackWriter> instancesIterable() {
+ return new Iterator<>() {
- private final Iterator<WeakReference<PackWriter>> it = instances
- .keySet().iterator();
+ private final Iterator<WeakReference<PackWriter>> it = instances
+ .keySet().iterator();
- private PackWriter next;
+ private PackWriter next;
- @Override
- public boolean hasNext() {
- if (next != null) {
- return true;
- }
- while (it.hasNext()) {
- WeakReference<PackWriter> ref = it.next();
- next = ref.get();
+ @Override
+ public boolean hasNext() {
if (next != null) {
return true;
}
- it.remove();
+ while (it.hasNext()) {
+ WeakReference<PackWriter> ref = it.next();
+ next = ref.get();
+ if (next != null) {
+ return true;
+ }
+ it.remove();
+ }
+ return false;
}
- return false;
- }
- @Override
- public PackWriter next() {
- if (hasNext()) {
- PackWriter result = next;
- next = null;
- return result;
+ @Override
+ public PackWriter next() {
+ if (hasNext()) {
+ PackWriter result = next;
+ next = null;
+ return result;
+ }
+ throw new NoSuchElementException();
}
- throw new NoSuchElementException();
- }
- @Override
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
/**
* Get all allocated, non-released PackWriters instances.
@@ -190,7 +192,7 @@ public class PackWriter implements AutoCloseable {
* @return all allocated, non-released PackWriters instances.
*/
public static Iterable<PackWriter> getInstances() {
- return instancesIterable;
+ return PackWriter::instancesIterable;
}
@SuppressWarnings("unchecked")
@@ -255,6 +257,8 @@ public class PackWriter implements AutoCloseable {
private boolean useBitmaps;
+ private boolean createBitmaps = true;
+
private boolean ignoreMissingUninteresting = true;
private boolean pruneCurrentObjectList;
@@ -299,19 +303,6 @@ public class PackWriter implements AutoCloseable {
}
/**
- * Create a writer to load objects from the specified reader.
- * <p>
- * Objects for packing are specified in {@link #preparePack(Iterator)} or
- * {@link #preparePack(ProgressMonitor, Set, Set)}.
- *
- * @param reader
- * reader to read from the repository with.
- */
- public PackWriter(ObjectReader reader) {
- this(new PackConfig(), reader);
- }
-
- /**
* Create writer for specified repository.
* <p>
* Objects for packing are specified in {@link #preparePack(Iterator)} or
@@ -576,6 +567,26 @@ public class PackWriter implements AutoCloseable {
}
/**
+ * Whether to generate bitmaps.
+ *
+ * @param createBitmaps
+ * if set to true, bitmaps will be generated when creating a pack.
+ */
+ public void setCreateBitmaps(boolean createBitmaps) {
+ this.createBitmaps = createBitmaps;
+ }
+
+ /**
+ * Whether the bitmap file is to be created by this PackWriter.
+ *
+ * @return {@code true} if the bitmap file is to be created by this
+ * PackWriter.
+ */
+ public boolean isCreateBitmaps() {
+ return createBitmaps;
+ }
+
+ /**
* Whether the index file cannot be created by this PackWriter.
*
* @return {@code true} if the index file cannot be created by this
@@ -656,7 +667,10 @@ public class PackWriter implements AutoCloseable {
}
/**
- * @param filter the filter which indicates what and what not this writer
+ * Set filter spec
+ *
+ * @param filter
+ * the filter which indicates what and what not this writer
* should include
*/
public void setFilterSpec(@NonNull FilterSpec filter) {
@@ -664,7 +678,10 @@ public class PackWriter implements AutoCloseable {
}
/**
- * @param config configuration related to packfile URIs
+ * Set packfile URI config
+ *
+ * @param config
+ * configuration related to packfile URIs
* @since 5.5
*/
public void setPackfileUriConfig(PackfileUriConfig config) {
@@ -1061,7 +1078,7 @@ public class PackWriter implements AutoCloseable {
if (indexVersion <= 0) {
for (BlockList<ObjectToPack> objs : objectsLists)
indexVersion = Math.max(indexVersion,
- PackIndexWriter.oldestPossibleFormat(objs));
+ BasePackIndexWriter.oldestPossibleFormat(objs));
}
return indexVersion;
}
@@ -1082,12 +1099,28 @@ public class PackWriter implements AutoCloseable {
* the index data could not be written to the supplied stream.
*/
public void writeIndex(OutputStream indexStream) throws IOException {
+ writeIndex(BasePackIndexWriter.createVersion(indexStream,
+ getIndexVersion()));
+ }
+
+ /**
+ * Create an index file to match the pack file just written.
+ * <p>
+ * Called after
+ * {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)}.
+ * <p>
+ * Writing an index is only required for local pack storage. Packs sent on
+ * the network do not need to create an index.
+ *
+ * @param iw
+ * an {@link PackIndexWriter} instance to write the index
+ * @throws java.io.IOException
+ * the index data could not be written to the supplied stream.
+ */
+ public void writeIndex(PackIndexWriter iw) throws IOException {
if (isIndexDisabled())
throw new IOException(JGitText.get().cachedPacksPreventsIndexCreation);
-
long writeStart = System.currentTimeMillis();
- final PackIndexWriter iw = PackIndexWriter.createVersion(
- indexStream, getIndexVersion());
iw.write(sortByName(), packcsum);
stats.timeWriting += System.currentTimeMillis() - writeStart;
}
@@ -1099,7 +1132,7 @@ public class PackWriter implements AutoCloseable {
* Called after
* {@link #writePack(ProgressMonitor, ProgressMonitor, OutputStream)} that
* populates the list of objects to pack and before
- * {@link #writeBitmapIndex(OutputStream)} that destroys it.
+ * {@link #writeBitmapIndex(PackBitmapIndexWriter)} that destroys it.
* <p>
* Writing this index is only required for local pack storage. Packs sent on
* the network do not need to create an object size index.
@@ -1137,24 +1170,54 @@ public class PackWriter implements AutoCloseable {
}
/**
+ * Whether the writer will write a reverse index file. The configuration
+ * flag must be on and the writer must be able to write corresponding
+ * forward index.
+ *
+ * @return whether the writer will write a reverse index file
+ */
+ public boolean isReverseIndexEnabled() {
+ // Only write the reverse index if the writer is configured to and the
+ // forward index that it would correspond to will be written.
+ return config.isWriteReverseIndex() && !isIndexDisabled();
+ }
+
+ /**
+ * Write the pack's reverse index file to the output stream.
+ *
+ * @param stream
+ * where to write the file contents to
+ * @throws IOException
+ * if writing to the stream fails
+ */
+ public void writeReverseIndex(OutputStream stream) throws IOException {
+ if (!isReverseIndexEnabled()) {
+ return;
+ }
+ long writeStart = System.currentTimeMillis();
+ PackReverseIndexWriter writer = PackReverseIndexWriter
+ .createWriter(stream);
+ writer.write(sortByName(), packcsum);
+ stats.timeWriting += System.currentTimeMillis() - writeStart;
+ }
+
+ /**
* Create a bitmap index file to match the pack file just written.
* <p>
* Called after {@link #prepareBitmapIndex(ProgressMonitor)}.
*
- * @param bitmapIndexStream
- * output for the bitmap index data. Caller is responsible for
- * closing this stream.
+ * @param bitmapIndexWriter
+ * a writer to store the bitmap index in this object database
* @throws java.io.IOException
- * the index data could not be written to the supplied stream.
+ * the index data could not be written using the supplied writer
*/
- public void writeBitmapIndex(OutputStream bitmapIndexStream)
+ public void writeBitmapIndex(PackBitmapIndexWriter bitmapIndexWriter)
throws IOException {
if (writeBitmaps == null)
throw new IOException(JGitText.get().bitmapsMustBePrepared);
long writeStart = System.currentTimeMillis();
- final PackBitmapIndexWriterV1 iw = new PackBitmapIndexWriterV1(bitmapIndexStream);
- iw.write(writeBitmaps, packcsum);
+ bitmapIndexWriter.write(writeBitmaps, packcsum);
stats.timeWriting += System.currentTimeMillis() - writeStart;
}
@@ -1690,6 +1753,11 @@ public class PackWriter implements AutoCloseable {
}
throw new IOException(JGitText
.get().packingCancelledDuringObjectsWriting, e);
+ } catch (Throwable e) {
+ if (e1 != null) {
+ e.addSuppressed(e1);
+ }
+ throw e;
}
}
}
@@ -1948,19 +2016,21 @@ public class PackWriter implements AutoCloseable {
final long countingStart = System.currentTimeMillis();
beginPhase(PackingPhase.COUNTING, countingMonitor, ProgressMonitor.UNKNOWN);
- stats.interestingObjects = Collections.unmodifiableSet(new HashSet<ObjectId>(want));
- stats.uninterestingObjects = Collections.unmodifiableSet(new HashSet<ObjectId>(have));
+ stats.interestingObjects = Collections
+ .unmodifiableSet(new HashSet<>(want));
+ stats.uninterestingObjects = Collections
+ .unmodifiableSet(new HashSet<>(have));
excludeFromBitmapSelection = noBitmaps;
canBuildBitmaps = config.isBuildBitmaps()
&& !shallowPack
&& have.isEmpty()
- && (excludeInPacks == null || excludeInPacks.length == 0);
+ && createBitmaps;
if (!shallowPack && useBitmaps) {
BitmapIndex bitmapIndex = reader.getBitmapIndex();
if (bitmapIndex != null) {
- BitmapWalker bitmapWalker = new BitmapWalker(
- walker, bitmapIndex, countingMonitor);
+ BitmapWalker bitmapWalker = new BitmapWalker(walker,
+ bitmapIndex, countingMonitor);
findObjectsToPackUsingBitmaps(bitmapWalker, want, have);
endPhase(countingMonitor);
stats.timeCounting = System.currentTimeMillis() - countingStart;
@@ -2248,7 +2318,7 @@ public class PackWriter implements AutoCloseable {
/**
* Determines if the object should be omitted from the pack as a result of
- * its depth (probably because of the tree:<depth> filter).
+ * its depth (probably because of the tree:&lt;depth&gt; filter).
* <p>
* Causes {@code walker} to skip traversing the current tree, which ought to
* have just started traversal, assuming this method is called as soon as a
@@ -2324,17 +2394,46 @@ public class PackWriter implements AutoCloseable {
* this method once for each representation available for an object, to
* allow the writer to find the most suitable one for the output.
*
+ * This method tries to take a very simple approach to avoiding delta chains
+ * during object reuse by selecting from the oldest pack that contains them.
+ * This helps select many objects from the same pack which helps make good
+ * use of any WindowCache caching, and it helps prevent cycles because a
+ * pack must not have a cycle in the delta chain. If both objects A and B
+ * are chosen out of the same source pack then there cannot be an A->B->A
+ * cycle.
+ *
+ * The oldest pack is also the most likely to have the smallest deltas. It
+ * generally is the biggest pack in the system and probably came from the
+ * clone (or last GC) of this repository, where all objects were previously
+ * considered and packed tightly together. If an object appears again (for
+ * example due to a revert and a push into this repository) the newer copy
+ * won't be nearly as small as the older delta version of it, even if the
+ * newer one is also itself a delta.
+ *
+ * Thus this method is optimized for being called in an order that presumes
+ * that earlier representations are better than later ones, and it expects
+ * representations from older pack files to be tested first, and it will
+ * shortcut any searching once it is satisfied with the selected
+ * representation. Perhaps ideally representation testing ordering should be
+ * based on packfile object count instead of age since file age can be
+ * altered, and be deceiving for other reasons. Perhaps the presence of a
+ * bitmap file for a pack file should prioritize it to be tested even
+ * earlier than object count?
+ *
* @param otp
* the object being packed.
* @param next
* the next available representation from the repository.
+ * @return whether the search should continue in the hopes of finding a
+ * better representation
*/
- public void select(ObjectToPack otp, StoredObjectRepresentation next) {
+ public boolean select(ObjectToPack otp, StoredObjectRepresentation next) {
int nFmt = next.getFormat();
if (!cachedPacks.isEmpty()) {
- if (otp.isEdge())
- return;
+ if (otp.isEdge()) {
+ return false;
+ }
if (nFmt == PACK_WHOLE || nFmt == PACK_DELTA) {
for (CachedPack pack : cachedPacks) {
if (pack.hasObject(otp, next)) {
@@ -2342,7 +2441,7 @@ public class PackWriter implements AutoCloseable {
otp.clearDeltaBase();
otp.clearReuseAsIs();
pruneCurrentObjectList = true;
- return;
+ return false;
}
}
}
@@ -2352,23 +2451,22 @@ public class PackWriter implements AutoCloseable {
ObjectId baseId = next.getDeltaBase();
ObjectToPack ptr = objectsMap.get(baseId);
if (ptr != null && !ptr.isEdge()) {
- otp.setDeltaBase(ptr);
- otp.setReuseAsIs();
- } else if (thin && have(ptr, baseId)) {
- otp.setDeltaBase(baseId);
- otp.setReuseAsIs();
- } else {
- otp.clearDeltaBase();
- otp.clearReuseAsIs();
+ return useDelta(otp, next, ptr);
+ }
+ if (thin && have(ptr, baseId)) {
+ return useDelta(otp, next, baseId);
}
+ otp.clearDeltaBase();
+ otp.clearReuseAsIs();
} else if (nFmt == PACK_WHOLE && config.isReuseObjects()) {
int nWeight = next.getWeight();
if (otp.isReuseAsIs() && !otp.isDeltaRepresentation()) {
// We've chosen another PACK_WHOLE format for this object,
// choose the one that has the smaller compressed size.
//
- if (otp.getWeight() <= nWeight)
- return;
+ if (otp.getWeight() < nWeight) {
+ return true;
+ }
}
otp.clearDeltaBase();
otp.setReuseAsIs();
@@ -2380,6 +2478,15 @@ public class PackWriter implements AutoCloseable {
otp.setDeltaAttempted(reuseDeltas && next.wasDeltaAttempted());
otp.select(next);
+ return true;
+ }
+
+ private boolean useDelta(ObjectToPack otp, StoredObjectRepresentation rep, ObjectId base) {
+ otp.setDeltaBase(base);
+ otp.setReuseAsIs();
+ otp.setDeltaAttempted(reuseDeltas && rep.wasDeltaAttempted());
+ otp.select(rep);
+ return false;
}
private final boolean have(ObjectToPack ptr, AnyObjectId objectId) {
@@ -2394,11 +2501,12 @@ public class PackWriter implements AutoCloseable {
* object graph at selected commits. Writing a bitmap index is an optional
* feature that not all pack users may require.
* <p>
- * Called after {@link #writeIndex(OutputStream)}.
+ * Called after {@link #writeIndex(PackIndexWriter)}.
* <p>
* To reduce memory internal state is cleared during this method, rendering
* the PackWriter instance useless for anything further than a call to write
- * out the new bitmaps with {@link #writeBitmapIndex(OutputStream)}.
+ * out the new bitmaps with
+ * {@link #writeBitmapIndex(PackBitmapIndexWriter)}.
*
* @param pm
* progress monitor to report bitmap building work.
@@ -2552,17 +2660,29 @@ public class PackWriter implements AutoCloseable {
this.bytesUsed = bytesUsed;
}
- /** @return the PackConfig used to build the writer. */
+ /**
+ * Get pack config
+ *
+ * @return the PackConfig used to build the writer.
+ */
public PackConfig getConfig() {
return config;
}
- /** @return the current phase of the writer. */
+ /**
+ * Get packing phase
+ *
+ * @return the current phase of the writer.
+ */
public PackingPhase getPhase() {
return phase;
}
- /** @return an estimate of the total memory used by the writer. */
+ /**
+ * Get estimate of memory by the writer
+ *
+ * @return an estimate of the total memory used by the writer.
+ */
public long estimateBytesUsed() {
return bytesUsed;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
index 5a18f1e567..bf87c4c9d6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriterBitmapPreparer.java
@@ -14,6 +14,7 @@ import static org.eclipse.jgit.internal.storage.file.PackBitmapIndex.FLAG_REUSE;
import static org.eclipse.jgit.revwalk.RevFlag.SEEN;
import java.io.IOException;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -77,6 +78,7 @@ class PackWriterBitmapPreparer {
private final int recentCommitSpan;
private final int distantCommitSpan;
private final int excessiveBranchCount;
+ private final int excessiveBranchTipCount;
private final long inactiveBranchTimestamp;
PackWriterBitmapPreparer(ObjectReader reader,
@@ -96,10 +98,12 @@ class PackWriterBitmapPreparer {
this.recentCommitSpan = config.getBitmapRecentCommitSpan();
this.distantCommitSpan = config.getBitmapDistantCommitSpan();
this.excessiveBranchCount = config.getBitmapExcessiveBranchCount();
- long now = SystemReader.getInstance().getCurrentTime();
- long ageInSeconds = config.getBitmapInactiveBranchAgeInDays()
+ this.excessiveBranchTipCount = Math.max(excessiveBranchCount,
+ config.getBitmapExcessiveBranchTipCount());
+ Instant now = SystemReader.getInstance().now();
+ long ageInSeconds = (long) config.getBitmapInactiveBranchAgeInDays()
* DAY_IN_SECONDS;
- this.inactiveBranchTimestamp = (now / 1000) - ageInSeconds;
+ this.inactiveBranchTimestamp = now.getEpochSecond() - ageInSeconds;
}
/**
@@ -163,11 +167,17 @@ class PackWriterBitmapPreparer {
rw2.setRetainBody(false);
rw2.setRevFilter(new NotInBitmapFilter(seen));
+ int newWantsCount = selectionHelper.newWantsByNewest.size();
+ int maxBranches = Math.min(excessiveBranchTipCount, newWantsCount);
+ Set<RevCommit> excessiveBranches = new HashSet<>(
+ selectionHelper.newWantsByNewest.subList(maxBranches,
+ newWantsCount));
// For each branch, do a revwalk to enumerate its commits. Exclude
// both reused commits and any commits seen in a previous branch.
// Then iterate through all new commits from oldest to newest,
// selecting well-spaced commits in this branch.
- for (RevCommit rc : selectionHelper.newWantsByNewest) {
+ for (RevCommit rc : selectionHelper.newWantsByNewest.subList(0,
+ maxBranches)) {
BitmapBuilder tipBitmap = commitBitmapIndex.newBitmapBuilder();
rw2.markStart((RevCommit) rw2.peel(rw2.parseAny(rc)));
RevCommit rc2;
@@ -223,7 +233,7 @@ class PackWriterBitmapPreparer {
pm.update(1);
// Always pick the items in wants, prefer merge commits.
- if (selectionHelper.newWants.remove(c)) {
+ if (!excessiveBranches.contains(c) && selectionHelper.newWants.remove(c)) {
if (nextIn > 0) {
nextFlg = 0;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java
index d07713db8e..e9ff02700d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java
@@ -32,6 +32,8 @@ import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.time.Instant;
+import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
@@ -245,9 +247,9 @@ class BlockReader {
private PersonIdent readPersonIdent() {
String name = readValueString();
String email = readValueString();
- long ms = readVarint64() * 1000;
- int tz = readInt16();
- return new PersonIdent(name, email, ms, tz);
+ long epochSeconds = readVarint64();
+ ZoneOffset tz = ZoneOffset.ofTotalSeconds(readInt16() * 60);
+ return new PersonIdent(name, email, Instant.ofEpochSecond(epochSeconds), tz);
}
void readBlock(BlockSource src, long pos, int fileBlockSize)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java
index e3c0fc94aa..0ddfa5798a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java
@@ -92,7 +92,7 @@ class BlockWriter {
}
}
- int cnt = (int) (Math.ceil(blockLimitBytes / avgBytesPerEntry));
+ int cnt = (int) Math.ceil(blockLimitBytes / avgBytesPerEntry);
return Math.min(cnt, 4096);
}
@@ -524,8 +524,8 @@ class BlockWriter {
this.oldId = oldId;
this.newId = newId;
- this.timeSecs = who.getWhen().getTime() / 1000L;
- this.tz = (short) who.getTimeZoneOffset();
+ this.timeSecs = who.getWhenAsInstant().getEpochSecond();
+ this.tz = (short) (who.getZoneOffset().getTotalSeconds() / 60);
this.name = who.getName().getBytes(UTF_8);
this.email = who.getEmailAddress().getBytes(UTF_8);
this.msg = message.getBytes(UTF_8);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/EmptyLogCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/EmptyLogCursor.java
index 7c06be876b..8700556d48 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/EmptyLogCursor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/EmptyLogCursor.java
@@ -16,31 +16,26 @@ import org.eclipse.jgit.lib.ReflogEntry;
/** Empty {@link LogCursor} with no results. */
class EmptyLogCursor extends LogCursor {
- /** {@inheritDoc} */
@Override
public boolean next() throws IOException {
return false;
}
- /** {@inheritDoc} */
@Override
public String getRefName() {
return null;
}
- /** {@inheritDoc} */
@Override
public long getUpdateIndex() {
return 0;
}
- /** {@inheritDoc} */
@Override
public ReflogEntry getReflogEntry() {
return null;
}
- /** {@inheritDoc} */
@Override
public void close() {
// Do nothing.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/LogCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/LogCursor.java
index f78975af34..f9c701ebe2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/LogCursor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/LogCursor.java
@@ -51,7 +51,6 @@ public abstract class LogCursor implements AutoCloseable {
@Nullable
public abstract ReflogEntry getReflogEntry();
- /** {@inheritDoc} */
@Override
public abstract void close();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java
index e210acf058..aeaf241710 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java
@@ -39,7 +39,6 @@ public class MergedReftable extends Reftable {
/**
* Initialize a merged table reader.
- * <p>
*
* @param tableStack
* stack of tables to read from. The base of the stack is at
@@ -91,7 +90,6 @@ public class MergedReftable extends Reftable {
return minUpdateIndex;
}
- /** {@inheritDoc} */
@Override
public boolean hasObjectMap() throws IOException {
boolean has = true;
@@ -101,7 +99,6 @@ public class MergedReftable extends Reftable {
return has;
}
- /** {@inheritDoc} */
@Override
public RefCursor allRefs() throws IOException {
MergedRefCursor m = new MergedRefCursor();
@@ -111,7 +108,6 @@ public class MergedReftable extends Reftable {
return m;
}
- /** {@inheritDoc} */
@Override
public RefCursor seekRef(String name) throws IOException {
MergedRefCursor m = new MergedRefCursor();
@@ -121,7 +117,6 @@ public class MergedReftable extends Reftable {
return m;
}
- /** {@inheritDoc} */
@Override
public RefCursor seekRefsWithPrefix(String prefix) throws IOException {
MergedRefCursor m = new MergedRefCursor();
@@ -131,7 +126,6 @@ public class MergedReftable extends Reftable {
return m;
}
- /** {@inheritDoc} */
@Override
public RefCursor byObjectId(AnyObjectId name) throws IOException {
MergedRefCursor m = new FilteringMergedRefCursor(name);
@@ -141,7 +135,6 @@ public class MergedReftable extends Reftable {
return m;
}
- /** {@inheritDoc} */
@Override
public LogCursor allLogs() throws IOException {
MergedLogCursor m = new MergedLogCursor();
@@ -151,7 +144,6 @@ public class MergedReftable extends Reftable {
return m;
}
- /** {@inheritDoc} */
@Override
public LogCursor seekLog(String refName, long updateIdx)
throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java
index 5e2c350883..6cd4f6c81d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/RefCursor.java
@@ -58,7 +58,6 @@ public abstract class RefCursor implements AutoCloseable {
return r.getStorage() == Ref.Storage.NEW && r.getObjectId() == null;
}
- /** {@inheritDoc} */
@Override
public abstract void close();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java
index 63786dc17e..882e537734 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java
@@ -138,8 +138,11 @@ public abstract class Reftable {
public abstract RefCursor byObjectId(AnyObjectId id) throws IOException;
/**
- * @return whether this reftable can do a fast SHA1 => ref lookup.
- * @throws IOException on I/O problems.
+ * Whether this reftable can do a fast SHA1 =&gt; ref lookup
+ *
+ * @return whether this reftable can do a fast SHA1 =&gt; ref lookup.
+ * @throws IOException
+ * on I/O problems.
*/
public abstract boolean hasObjectMap() throws IOException;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableBatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableBatchRefUpdate.java
index dbc2ec7dc9..da1b056342 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableBatchRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableBatchRefUpdate.java
@@ -64,7 +64,7 @@ public abstract class ReftableBatchRefUpdate extends BatchRefUpdate {
/**
* Initialize.
*
- * @param refdb
+ * @param refDb
* The RefDatabase
* @param reftableDb
* The ReftableDatabase
@@ -73,15 +73,14 @@ public abstract class ReftableBatchRefUpdate extends BatchRefUpdate {
* @param repository
* The repository on which this update will run
*/
- protected ReftableBatchRefUpdate(RefDatabase refdb, ReftableDatabase reftableDb, Lock lock,
- Repository repository) {
- super(refdb);
+ protected ReftableBatchRefUpdate(RefDatabase refDb,
+ ReftableDatabase reftableDb, Lock lock, Repository repository) {
+ super(refDb);
this.refDb = reftableDb;
this.lock = lock;
this.repository = repository;
}
- /** {@inheritDoc} */
@Override
public void execute(RevWalk rw, ProgressMonitor pm, List<String> options) {
List<ReceiveCommand> pending = getPending();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java
index 3c4bc75792..7e5f4ebbd4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java
@@ -12,6 +12,7 @@ package org.eclipse.jgit.internal.storage.reftable;
import java.io.IOException;
import java.io.OutputStream;
+import java.time.Instant;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
@@ -28,20 +29,26 @@ import org.eclipse.jgit.lib.ReflogEntry;
* to shadow any lower reftable that may have the reference present.
* <p>
* By default all log entries within the range defined by
- * {@link #setReflogExpireMinUpdateIndex(long)} and {@link #setReflogExpireMaxUpdateIndex(long)} are
- * copied, even if no references in the output file match the log records.
- * Callers may truncate the log to a more recent time horizon with
- * {@link #setReflogExpireOldestReflogTimeMillis(long)}, or disable the log altogether with
- * {@code setOldestReflogTimeMillis(Long.MAX_VALUE)}.
+ * {@link #setReflogExpireMinUpdateIndex(long)} and
+ * {@link #setReflogExpireMaxUpdateIndex(long)} are copied, even if no
+ * references in the output file match the log records. Callers may truncate the
+ * log to a more recent time horizon with
+ * {@link #setReflogExpireOlderThan(Instant)}, or disable the log
+ * altogether with {@code setReflogExpireOldestReflogTime(Instant.MAX)}.
*/
public class ReftableCompactor {
private final ReftableWriter writer;
+
private final ArrayDeque<ReftableReader> tables = new ArrayDeque<>();
private boolean includeDeletes;
+
private long reflogExpireMinUpdateIndex = 0;
+
private long reflogExpireMaxUpdateIndex = Long.MAX_VALUE;
- private long reflogExpireOldestReflogTimeMillis;
+
+ private Instant reflogExpireOldestReflogTime = Instant.EPOCH;
+
private Stats stats;
/**
@@ -122,9 +129,29 @@ public class ReftableCompactor {
* entries that predate {@code timeMillis} will be discarded.
* Specified in Java standard milliseconds since the epoch.
* @return {@code this}
+ *
+ * @deprecated Use {@link #setReflogExpireOlderThan(Instant)} instead
+ */
+ @Deprecated(since="7.3")
+ public ReftableCompactor setReflogExpireOldestReflogTimeMillis(
+ long timeMillis) {
+ return setReflogExpireOlderThan(timeMillis == Long.MAX_VALUE
+ ? Instant.MAX
+ : Instant.ofEpochMilli(timeMillis));
+ }
+
+ /**
+ * Set oldest reflog time to preserve.
+ *
+ * @param cutTime
+ * oldest log time to preserve. Entries whose timestamps are
+ * {@code >= cutTime} will be copied into the output file. Log
+ * entries that predate {@code cutTime} will be discarded.
+ * @return {@code this}
*/
- public ReftableCompactor setReflogExpireOldestReflogTimeMillis(long timeMillis) {
- reflogExpireOldestReflogTimeMillis = timeMillis;
+ public ReftableCompactor setReflogExpireOlderThan(
+ Instant cutTime) {
+ reflogExpireOldestReflogTime = cutTime;
return this;
}
@@ -182,14 +209,15 @@ public class ReftableCompactor {
}
private void mergeLogs(MergedReftable mr) throws IOException {
- if (reflogExpireOldestReflogTimeMillis == Long.MAX_VALUE) {
+ if (reflogExpireOldestReflogTime == Instant.MAX) {
return;
}
try (LogCursor lc = mr.allLogs()) {
while (lc.next()) {
long updateIndex = lc.getUpdateIndex();
- if (updateIndex > reflogExpireMaxUpdateIndex || updateIndex < reflogExpireMinUpdateIndex) {
+ if (updateIndex > reflogExpireMaxUpdateIndex
+ || updateIndex < reflogExpireMinUpdateIndex) {
continue;
}
@@ -203,14 +231,9 @@ public class ReftableCompactor {
}
PersonIdent who = log.getWho();
- if (who.getWhen().getTime() >= reflogExpireOldestReflogTimeMillis) {
- writer.writeLog(
- refName,
- updateIndex,
- who,
- log.getOldId(),
- log.getNewId(),
- log.getComment());
+ if (who.getWhenAsInstant().compareTo(reflogExpireOldestReflogTime) >= 0) {
+ writer.writeLog(refName, updateIndex, who, log.getOldId(),
+ log.getNewId(), log.getComment());
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableDatabase.java
index 0c16828617..bd4e2d6763 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableDatabase.java
@@ -52,6 +52,8 @@ public abstract class ReftableDatabase {
protected abstract MergedReftable openMergedReftable() throws IOException;
/**
+ * Get next update index
+ *
* @return the next available logical timestamp for an additional reftable
* in the stack.
* @throws java.io.IOException
@@ -67,6 +69,8 @@ public abstract class ReftableDatabase {
}
/**
+ * Get ReflogReader
+ *
* @return a ReflogReader for the given ref
* @param refname
* the name of the ref.
@@ -83,6 +87,8 @@ public abstract class ReftableDatabase {
}
/**
+ * Create a ReceiveCommand for the change from oldRef to newRef
+ *
* @return a ReceiveCommand for the change from oldRef to newRef
* @param oldRef
* a ref
@@ -136,6 +142,8 @@ public abstract class ReftableDatabase {
}
/**
+ * Get lock
+ *
* @return the lock protecting underlying ReftableReaders against concurrent
* reads.
*/
@@ -144,6 +152,8 @@ public abstract class ReftableDatabase {
}
/**
+ * Get reader for merged reftable
+ *
* @return the merged reftable that is implemented by the stack of
* reftables. Return value must be accessed under lock.
* @throws IOException
@@ -161,6 +171,8 @@ public abstract class ReftableDatabase {
}
/**
+ * Whether refName is conflicting
+ *
* @return whether the given refName would be illegal in a repository that
* uses loose refs.
* @param refName
@@ -268,14 +280,17 @@ public abstract class ReftableDatabase {
}
/**
- * Returns refs whose names start with a given prefix excluding all refs that
- * start with one of the given prefixes.
+ * Returns refs whose names start with a given prefix excluding all refs
+ * that start with one of the given prefixes.
*
- * @param include string that names of refs should start with; may be empty.
- * @param excludes strings that names of refs can't start with; may be empty.
+ * @param include
+ * string that names of refs should start with; may be empty.
+ * @param excludes
+ * strings that names of refs can't start with; may be empty.
* @return immutable list of refs whose names start with {@code include} and
- * none of the strings in {@code exclude}.
- * @throws java.io.IOException the reference space cannot be accessed.
+ * none of the strings in {@code excludes}.
+ * @throws java.io.IOException
+ * the reference space cannot be accessed.
*/
public List<Ref> getRefsByPrefixWithExclusions(String include, Set<String> excludes) throws IOException {
if (excludes.isEmpty()) {
@@ -316,8 +331,11 @@ public abstract class ReftableDatabase {
}
/**
+ * Whether there is a fast SHA1 to ref map
+ *
* @return whether there is a fast SHA1 to ref map.
- * @throws IOException in case of I/O problems.
+ * @throws IOException
+ * in case of I/O problems.
*/
public boolean hasFastTipsWithSha1() throws IOException {
lock.lock();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableOutputStream.java
index aa3aba516f..4aea3e684e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableOutputStream.java
@@ -59,14 +59,12 @@ class ReftableOutputStream extends OutputStream {
blockSize = bs;
}
- /** {@inheritDoc} */
@Override
public void write(int b) {
ensureBytesAvailableInBlockBuf(1);
blockBuf[cur++] = (byte) b;
}
- /** {@inheritDoc} */
@Override
public void write(byte[] b, int off, int cnt) {
ensureBytesAvailableInBlockBuf(cnt);
@@ -86,7 +84,11 @@ class ReftableOutputStream extends OutputStream {
return paddingUsed;
}
- /** @return bytes flushed; excludes {@link #bytesWrittenInBlock()}. */
+ /**
+ * Get size
+ *
+ * @return bytes flushed; excludes {@link #bytesWrittenInBlock()}.
+ */
long size() {
return out.getCount();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java
index cabb2e1842..2a73efd962 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java
@@ -127,7 +127,6 @@ public class ReftableReader extends Reftable implements AutoCloseable {
return maxUpdateIndex;
}
- /** {@inheritDoc} */
@Override
public RefCursor allRefs() throws IOException {
if (blockSize == -1) {
@@ -144,7 +143,6 @@ public class ReftableReader extends Reftable implements AutoCloseable {
return i;
}
- /** {@inheritDoc} */
@Override
public RefCursor seekRef(String refName) throws IOException {
initRefIndex();
@@ -155,7 +153,6 @@ public class ReftableReader extends Reftable implements AutoCloseable {
return i;
}
- /** {@inheritDoc} */
@Override
public RefCursor seekRefsWithPrefix(String prefix) throws IOException {
initRefIndex();
@@ -166,7 +163,6 @@ public class ReftableReader extends Reftable implements AutoCloseable {
return i;
}
- /** {@inheritDoc} */
@Override
public RefCursor byObjectId(AnyObjectId id) throws IOException {
initObjIndex();
@@ -179,7 +175,6 @@ public class ReftableReader extends Reftable implements AutoCloseable {
return i;
}
- /** {@inheritDoc} */
@Override
public LogCursor allLogs() throws IOException {
initLogIndex();
@@ -192,7 +187,6 @@ public class ReftableReader extends Reftable implements AutoCloseable {
return new EmptyLogCursor();
}
- /** {@inheritDoc} */
@Override
public LogCursor seekLog(String refName, long updateIndex)
throws IOException {
@@ -460,7 +454,6 @@ public class ReftableReader extends Reftable implements AutoCloseable {
return src.size();
}
- /** {@inheritDoc} */
@Override
public void close() throws IOException {
src.close();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReflogReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReflogReader.java
index f9f1def958..597303301a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReflogReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReflogReader.java
@@ -34,7 +34,6 @@ public class ReftableReflogReader implements ReflogReader {
this.refname = refname;
}
- /** {@inheritDoc} */
@Override
public ReflogEntry getLastEntry() throws IOException {
lock.lock();
@@ -46,13 +45,11 @@ public class ReftableReflogReader implements ReflogReader {
}
}
- /** {@inheritDoc} */
@Override
public List<ReflogEntry> getReverseEntries() throws IOException {
return getReverseEntries(Integer.MAX_VALUE);
}
- /** {@inheritDoc} */
@Override
public ReflogEntry getReverseEntry(int number) throws IOException {
lock.lock();
@@ -72,7 +69,6 @@ public class ReftableReflogReader implements ReflogReader {
}
}
- /** {@inheritDoc} */
@Override
public List<ReflogEntry> getReverseEntries(int max) throws IOException {
lock.lock();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java
index f4df0b28f7..b47f58e1a5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java
@@ -565,92 +565,163 @@ public class ReftableWriter {
objIndexLevels = objIdx != null ? objIdx.levels : 0;
}
- /** @return number of bytes in a ref block. */
+ /**
+ * Get ref block size
+ *
+ * @return number of bytes in a ref block.
+ */
public int refBlockSize() {
return refBlockSize;
}
- /** @return number of bytes to compress into a log block. */
+ /**
+ * Get log block size
+ *
+ * @return number of bytes to compress into a log block.
+ */
public int logBlockSize() {
return logBlockSize;
}
- /** @return number of references between binary search markers. */
+ /**
+ * Get restart interval
+ *
+ * @return number of references between binary search markers.
+ */
public int restartInterval() {
return restartInterval;
}
- /** @return smallest update index contained in this reftable. */
+ /**
+ * Get smallest update index
+ *
+ * @return smallest update index contained in this reftable.
+ */
public long minUpdateIndex() {
return minUpdateIndex;
}
- /** @return largest update index contained in this reftable. */
+ /**
+ * Get largest updated index
+ *
+ * @return largest update index contained in this reftable.
+ */
public long maxUpdateIndex() {
return maxUpdateIndex;
}
- /** @return total number of references in the reftable. */
+ /**
+ * Get number of refs
+ *
+ * @return total number of references in the reftable.
+ */
public long refCount() {
return refCnt;
}
- /** @return number of unique objects in the reftable. */
+ /**
+ * Get number of unique objects
+ *
+ * @return number of unique objects in the reftable.
+ */
public long objCount() {
return objCnt;
}
- /** @return total number of log records in the reftable. */
+ /**
+ * Get number of log records
+ *
+ * @return total number of log records in the reftable.
+ */
public long logCount() {
return logCnt;
}
- /** @return number of bytes for references, including ref index. */
+ /**
+ * Get number of bytes for references
+ *
+ * @return number of bytes for references, including ref index.
+ */
public long refBytes() {
return refBytes;
}
- /** @return number of bytes for objects, including object index. */
+ /**
+ * Get number of bytes for objects
+ *
+ * @return number of bytes for objects, including object index.
+ */
public long objBytes() {
return objBytes;
}
- /** @return number of bytes for log, including log index. */
+ /**
+ * Get number of bytes for log
+ *
+ * @return number of bytes for log, including log index.
+ */
public long logBytes() {
return logBytes;
}
- /** @return total number of bytes in the reftable. */
+ /**
+ * Get total number of bytes
+ *
+ * @return total number of bytes in the reftable.
+ */
public long totalBytes() {
return totalBytes;
}
- /** @return bytes of padding used to maintain block alignment. */
+ /**
+ * Get number of padding bytes
+ *
+ * @return bytes of padding used to maintain block alignment.
+ */
public long paddingBytes() {
return paddingUsed;
}
- /** @return number of bytes in the ref index; 0 if no index was used. */
+ /**
+ * Get size of ref index
+ *
+ * @return number of bytes in the ref index; 0 if no index was used.
+ */
public int refIndexSize() {
return refIndexSize;
}
- /** @return number of levels in the ref index. */
+ /**
+ * Get number of ref index levels
+ *
+ * @return number of levels in the ref index.
+ */
public int refIndexLevels() {
return refIndexLevels;
}
- /** @return number of bytes in the object index; 0 if no index. */
+ /**
+ * Get size of object index
+ *
+ * @return number of bytes in the object index; 0 if no index.
+ */
public int objIndexSize() {
return objIndexSize;
}
- /** @return number of levels in the object index. */
+ /**
+ * Get number of levels in object index
+ *
+ * @return number of levels in the object index.
+ */
public int objIndexLevels() {
return objIndexLevels;
}
/**
+ * Get number of bytes required to uniquely identify all objects in the
+ * reftable
+ *
* @return number of bytes required to uniquely identify all objects in
* the reftable. Unique abbreviations in hex would be
* {@code 2 * objIdLength()}.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java
index d0e24413bb..a4740c9bf6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/submodule/SubmoduleValidator.java
@@ -45,8 +45,8 @@ public class SubmoduleValidator {
* @param message
* Description of the problem
* @param fsckMessageId
- * Error identifier, following the git fsck fsck.<msg-id>
- * format
+ * Error identifier, following the git fsck
+ * fsck.&lt;msg-id&gt; format
*/
SubmoduleValidationException(String message,
ObjectChecker.ErrorType fsckMessageId) {
@@ -56,6 +56,8 @@ public class SubmoduleValidator {
/**
+ * Get the error identifier
+ *
* @return the error identifier
*/
public ObjectChecker.ErrorType getFsckMessageId() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/http/NetscapeCookieFileCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/http/NetscapeCookieFileCache.java
index 69a8273158..930bcea75b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/http/NetscapeCookieFileCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/http/NetscapeCookieFileCache.java
@@ -39,6 +39,8 @@ public class NetscapeCookieFileCache {
}
/**
+ * Get singleton instance
+ *
* @param config
* the config which defines the limit for this cache
* @return the singleton instance of the cookie file cache. If the cache has
@@ -54,6 +56,8 @@ public class NetscapeCookieFileCache {
}
/**
+ * Get a cache entry
+ *
* @param path
* the path of the cookie file to retrieve
* @return the cache entry belonging to the requested file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstCommand.java
index c75cf5d618..a196fbe418 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstCommand.java
@@ -69,13 +69,21 @@ public final class FirstCommand {
this.capabilities = capabilities;
}
- /** @return non-capabilities part of the line. */
+ /**
+ * Get line
+ *
+ * @return non-capabilities part of the line.
+ */
@NonNull
public String getLine() {
return line;
}
- /** @return capabilities parsed from the line, as an immutable map. */
+ /**
+ * Get capabilities
+ *
+ * @return capabilities parsed from the line, as an immutable map.
+ */
@NonNull
public Map<String, String> getCapabilities() {
return capabilities;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstWant.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstWant.java
index 30d629665f..e214cdae8b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstWant.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/parser/FirstWant.java
@@ -102,12 +102,18 @@ public class FirstWant {
this.clientSID = clientSID;
}
- /** @return non-capabilities part of the line. */
+ /**
+ * Get line
+ *
+ * @return non-capabilities part of the line.
+ */
public String getLine() {
return line;
}
/**
+ * Get capabilities
+ *
* @return capabilities parsed from the line as an immutable set (excluding
* agent and session-id).
*/
@@ -115,13 +121,21 @@ public class FirstWant {
return capabilities;
}
- /** @return client user agent parsed from the line. */
+ /**
+ * Get agent
+ *
+ * @return client user agent parsed from the line.
+ */
@Nullable
public String getAgent() {
return agent;
}
- /** @return client session-id parsed from the line. */
+ /**
+ * Get client session-id
+ *
+ * @return client session-id parsed from the line.
+ */
@Nullable
public String getClientSID() {
return clientSID;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
index 659ccb8c55..542d6e94f3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java
@@ -22,7 +22,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -100,7 +99,7 @@ public class OpenSshConfigFile implements SshConfigStore {
* fully resolved entries created from that.
*/
private static class State {
- List<HostEntry> entries = new LinkedList<>();
+ List<HostEntry> entries = new ArrayList<>();
// Previous lookups, keyed by user@hostname:port
Map<String, HostEntry> hosts = new HashMap<>();
@@ -218,7 +217,7 @@ public class OpenSshConfigFile implements SshConfigStore {
private List<HostEntry> parse(BufferedReader reader)
throws IOException {
- final List<HostEntry> entries = new LinkedList<>();
+ final List<HostEntry> entries = new ArrayList<>();
// The man page doesn't say so, but the openssh parser (readconf.c)
// starts out in active mode and thus always applies any lines that
@@ -428,7 +427,35 @@ public class OpenSshConfigFile implements SshConfigStore {
return value;
}
- private static boolean patternMatchesHost(String pattern, String name) {
+ /**
+ * Tells whether a given {@code name} matches the given list of patterns,
+ * accounting for negative matches.
+ *
+ * @param patterns
+ * to test {@code name} against; any pattern starting with an
+ * exclamation mark is a negative pattern
+ * @param name
+ * to test
+ * @return {@code true} if the {@code name} matches at least one of the
+ * non-negative patterns and none of the negative patterns,
+ * {@code false} otherwise
+ * @since 7.1
+ */
+ public static boolean patternMatch(Iterable<String> patterns, String name) {
+ boolean doesMatch = false;
+ for (String pattern : patterns) {
+ if (pattern.startsWith("!")) { //$NON-NLS-1$
+ if (patternMatches(pattern.substring(1), name)) {
+ return false;
+ }
+ } else if (!doesMatch && patternMatches(pattern, name)) {
+ doesMatch = true;
+ }
+ }
+ return doesMatch;
+ }
+
+ private static boolean patternMatches(String pattern, String name) {
if (pattern.indexOf('*') >= 0 || pattern.indexOf('?') >= 0) {
final FileNameMatcher fn;
try {
@@ -681,18 +708,7 @@ public class OpenSshConfigFile implements SshConfigStore {
}
boolean matches(String hostName) {
- boolean doesMatch = false;
- for (String pattern : patterns) {
- if (pattern.startsWith("!")) { //$NON-NLS-1$
- if (patternMatchesHost(pattern.substring(1), hostName)) {
- return false;
- }
- } else if (!doesMatch
- && patternMatchesHost(pattern, hostName)) {
- doesMatch = true;
- }
- }
- return doesMatch;
+ return patternMatch(patterns, hostName);
}
private static String toKey(String key) {
@@ -1154,7 +1170,6 @@ public class OpenSshConfigFile implements SshConfigStore {
}
}
- /** {@inheritDoc} */
@Override
@SuppressWarnings("nls")
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/CleanupService.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/CleanupService.java
new file mode 100644
index 0000000000..29ed7564d3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/CleanupService.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024, Thomas Wolf <twolf@apache.org> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.util;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A class that is registered as an OSGi service via the manifest. If JGit runs
+ * in OSGi, OSGi will instantiate a singleton as soon as the bundle is activated
+ * since this class is an immediate OSGi component with no dependencies. OSGi
+ * will then call its {@link #start()} method. If JGit is not running in OSGi,
+ * {@link #getInstance()} will lazily create an instance.
+ * <p>
+ * An OSGi-created {@link CleanupService} will run the registered cleanup when
+ * the {@code org.eclipse.jgit} bundle is deactivated. A lazily created instance
+ * will register the cleanup as a JVM shutdown hook.
+ * </p>
+ */
+public final class CleanupService {
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(CleanupService.class);
+
+ private static final Object LOCK = new Object();
+
+ private static CleanupService INSTANCE;
+
+ private final boolean isOsgi;
+
+ private JGitText jgitText;
+
+ private Runnable cleanup;
+
+ /**
+ * Public component constructor for OSGi DS. Do <em>not</em> call this
+ * explicitly! (Unfortunately this constructor must be public because of
+ * OSGi requirements.)
+ */
+ public CleanupService() {
+ this.isOsgi = true;
+ setInstance(this);
+ }
+
+ private CleanupService(boolean isOsgi) {
+ this.isOsgi = isOsgi;
+ }
+
+ private static void setInstance(CleanupService service) {
+ synchronized (LOCK) {
+ INSTANCE = service;
+ }
+ }
+
+ /**
+ * Obtains the singleton instance of the {@link CleanupService} that knows
+ * whether or not it is running on OSGi.
+ *
+ * @return the {@link CleanupService} singleton instance
+ */
+ public static CleanupService getInstance() {
+ synchronized (LOCK) {
+ if (INSTANCE == null) {
+ INSTANCE = new CleanupService(false);
+ }
+ return INSTANCE;
+ }
+ }
+
+ void start() {
+ // Nothing to do
+ }
+
+ void register(Runnable cleanUp) {
+ if (isOsgi) {
+ cleanup = cleanUp;
+ } else {
+ // Ensure the JGitText class is loaded. Depending on the framework
+ // JGit runs in, it may not be possible anymore to load classes when
+ // the hook runs. For instance when run in a maven plug-in: the
+ // Plexus class world that loaded JGit may already have been
+ // disposed by the time the JVM shutdown hook runs when the whole
+ // maven build terminates.
+ jgitText = JGitText.get();
+ assert jgitText != null;
+ try {
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ try {
+ cleanUp.run();
+ // Don't catch exceptions; let the JVM do the problem
+ // reporting.
+ } finally {
+ jgitText = null;
+ }
+ }));
+ } catch (IllegalStateException e) {
+ // Ignore -- the JVM is already shutting down.
+ }
+ }
+ }
+
+ void shutDown() {
+ if (isOsgi && cleanup != null) {
+ Runnable r = cleanup;
+ cleanup = null;
+ try {
+ r.run();
+ } catch (RuntimeException e) {
+ LOG.error(JGitText.get().shutdownCleanupFailed, e);
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/Optionally.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/Optionally.java
new file mode 100644
index 0000000000..270b760562
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/Optionally.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, 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 v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.util;
+
+import java.lang.ref.SoftReference;
+import java.util.Optional;
+
+/**
+ * Interface representing a reference to a potentially mutable optional object.
+ *
+ * @param <T>
+ * type of the mutable optional object
+ *
+ * @since 6.7
+ */
+public interface Optionally<T> {
+ /**
+ * A permanently empty Optionally
+ *
+ * @param <T>
+ * type of the mutable optional object
+ *
+ */
+ public class Empty<T> implements Optionally<T> {
+ @Override
+ public void clear() {
+ // empty
+ }
+
+ @Override
+ public Optional<T> getOptional() {
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * A permanent(hard) reference to an object
+ *
+ * @param <T>
+ * type of the mutable optional object
+ *
+ */
+ public class Hard<T> implements Optionally<T> {
+ /**
+ * The mutable optional object
+ */
+ protected Optional<T> optional;
+
+ /**
+ * @param element
+ * the mutable optional object
+ */
+ public Hard(T element) {
+ optional = Optional.ofNullable(element);
+ }
+
+ @Override
+ public void clear() {
+ optional = Optional.empty();
+ }
+
+ @Override
+ public Optional<T> getOptional() {
+ return optional;
+ }
+ }
+
+ /**
+ * A SoftReference Optionally
+ *
+ * @param <T>
+ * type of the mutable optional object
+ *
+ */
+ public class Soft<T> extends SoftReference<T> implements Optionally<T> {
+ /**
+ * @param t
+ * the mutable optional object
+ */
+ public Soft(T t) {
+ super(t);
+ }
+
+ @Override
+ public Optional<T> getOptional() {
+ return Optional.ofNullable(get());
+ }
+ }
+
+ /**
+ * The empty Optionally
+ */
+ public static final Optionally<?> EMPTY = new Empty<>();
+
+ /**
+ * Get empty Optionally
+ *
+ * @param <T>
+ * type of the empty Optionally
+ * @return the empty Optionally
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Optionally<T> empty() {
+ return (Optionally<T>) EMPTY;
+ }
+
+ /**
+ * Clear the object
+ */
+ void clear();
+
+ /**
+ * Get an Optional representing the current state of the object
+ *
+ * @return the mutable optional object
+ */
+ Optional<T> getOptional();
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/ShutdownHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/ShutdownHook.java
new file mode 100644
index 0000000000..f6b4723489
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/util/ShutdownHook.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023, SAP SE and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.util;
+
+import java.text.MessageFormat;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The singleton {@link ShutdownHook} provides a means to register
+ * {@link Listener}s that are run when JGit is uninstalled, either
+ * <ul>
+ * <li>in an OSGi framework when this bundle is deactivated, or</li>
+ * <li>otherwise, when the JVM as a whole shuts down.</li>
+ * </ul>
+ */
+@SuppressWarnings("ImmutableEnumChecker")
+public enum ShutdownHook {
+ /**
+ * Singleton
+ */
+ INSTANCE;
+
+ /**
+ * Object that needs to cleanup on shutdown.
+ */
+ public interface Listener {
+ /**
+ * Cleanup resources when JGit is shut down.
+ * <p>
+ * Implementations should be coded defensively
+ * <ul>
+ * <li>they should finish their work quickly
+ * <li>they should be written to be thread-safe and to avoid deadlocks
+ * insofar as possible
+ * <li>they should not rely blindly upon services that may have
+ * registered their own shutdown hooks and therefore may themselves be
+ * in the process of shutting down
+ * <li>attempts to use other thread-based services may lead to
+ * deadlocks.
+ * </ul>
+ * See {@link Runtime#addShutdownHook} for more details.
+ */
+ public void onShutdown();
+ }
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(ShutdownHook.class);
+
+ private final Set<Listener> listeners = ConcurrentHashMap.newKeySet();
+
+ private final AtomicBoolean shutdownInProgress = new AtomicBoolean();
+
+ private ShutdownHook() {
+ CleanupService.getInstance().register(this::cleanup);
+ }
+
+ private void cleanup() {
+ if (!shutdownInProgress.getAndSet(true)) {
+ ExecutorService runner = Executors.newWorkStealingPool();
+ try {
+ runner.submit(() -> {
+ this.doCleanup();
+ return null;
+ }).get(30L, TimeUnit.SECONDS);
+ } catch (InterruptedException | ExecutionException
+ | TimeoutException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ } finally {
+ runner.shutdownNow();
+ }
+ }
+ }
+
+ private void doCleanup() {
+ listeners.parallelStream().forEach(this::notify);
+ }
+
+ private void notify(Listener l) {
+ LOG.debug(JGitText.get().shutdownCleanup, l);
+ try {
+ l.onShutdown();
+ } catch (RuntimeException e) {
+ LOG.error(MessageFormat.format(
+ JGitText.get().shutdownCleanupListenerFailed, l), e);
+ }
+ }
+
+ /**
+ * Register object that needs cleanup during JGit shutdown if it is not
+ * already registered. Registration is disabled when JGit shutdown is
+ * already in progress.
+ *
+ * @param l
+ * the object to call {@link Listener#onShutdown} on when JGit
+ * shuts down
+ * @return {@code true} if this object has been registered
+ */
+ public boolean register(Listener l) {
+ if (shutdownInProgress.get()) {
+ return listeners.contains(l);
+ }
+ LOG.debug("register {} with shutdown hook", l); //$NON-NLS-1$
+ listeners.add(l);
+ return true;
+ }
+
+ /**
+ * Unregister object that no longer needs cleanup during JGit shutdown if it
+ * is still registered. Unregistration is disabled when JGit shutdown is
+ * already in progress.
+ *
+ * @param l
+ * the object registered to be notified for cleanup when the JVM
+ * shuts down
+ * @return {@code true} if this object is no longer registered
+ */
+ public boolean unregister(Listener l) {
+ if (shutdownInProgress.get()) {
+ return !listeners.contains(l);
+ }
+ LOG.debug("unregister {} from shutdown hook", l); //$NON-NLS-1$
+ listeners.remove(l);
+ return true;
+ }
+
+ /**
+ * Whether a JGit shutdown is in progress
+ *
+ * @return {@code true} if a JGit shutdown is in progress
+ */
+ public boolean isShutdownInProgress() {
+ return shutdownInProgress.get();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbrevConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbrevConfig.java
index 9109cfd769..c3dffdfe79 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbrevConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbrevConfig.java
@@ -19,7 +19,7 @@ import org.eclipse.jgit.internal.JGitText;
/**
* Git configuration option <a
- * href=https://git-scm.com/docs/git-config#Documentation/git-config.txt-coreabbrev">
+ * href="https://git-scm.com/docs/git-config#Documentation/git-config.txt-coreabbrev">
* core.abbrev</a>
*
* @since 6.1
@@ -100,6 +100,7 @@ public final class AbbrevConfig {
* object names to stay unique for some time.
*
* @param repo
+ * the repository the AbbrevConfig shall be computed for
* @return appropriate value computed based on the approximate number of
* packed objects in a repository
*/
@@ -128,7 +129,10 @@ public final class AbbrevConfig {
private int abbrev;
/**
+ * Create an {@code AbbrevConfig}
+ *
* @param abbrev
+ * abbreviation length
*/
private AbbrevConfig(int abbrev) {
this.abbrev = capAbbrev(abbrev);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java
index dc5e5cc20f..a13136b9f1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java
@@ -321,13 +321,11 @@ public final class AbbreviatedObjectId implements Serializable {
return mask(nibbles, word, v);
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
return w1;
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object o) {
if (o instanceof AbbreviatedObjectId) {
@@ -366,7 +364,6 @@ public final class AbbreviatedObjectId implements Serializable {
return new String(b, 0, nibbles);
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java
index 7685b30f8e..f742e993a0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java
@@ -35,23 +35,6 @@ public abstract class AnyObjectId implements Comparable<AnyObjectId> {
* @param secondObjectId
* the second identifier to compare. Must not be null.
* @return true if the two identifiers are the same.
- * @deprecated use {@link #isEqual(AnyObjectId, AnyObjectId)} instead
- */
- @Deprecated
- @SuppressWarnings("AmbiguousMethodReference")
- public static boolean equals(final AnyObjectId firstObjectId,
- final AnyObjectId secondObjectId) {
- return isEqual(firstObjectId, secondObjectId);
- }
-
- /**
- * Compare two 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.
* @since 5.4
*/
public static boolean isEqual(final AnyObjectId firstObjectId,
@@ -248,7 +231,6 @@ public abstract class AnyObjectId implements Comparable<AnyObjectId> {
return abbr.prefixCompare(this) == 0;
}
- /** {@inheritDoc} */
@Override
public final int hashCode() {
return w2;
@@ -266,7 +248,6 @@ public abstract class AnyObjectId implements Comparable<AnyObjectId> {
return other != null ? isEqual(this, other) : false;
}
- /** {@inheritDoc} */
@Override
public final boolean equals(Object o) {
if (o instanceof AnyObjectId) {
@@ -477,7 +458,6 @@ public abstract class AnyObjectId implements Comparable<AnyObjectId> {
dst[o--] = '0';
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
index b2242a11ca..0c1da83dfb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
@@ -13,19 +13,23 @@ package org.eclipse.jgit.lib;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BARE;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WORKTREE;
+import static org.eclipse.jgit.lib.Constants.CONFIG;
import static org.eclipse.jgit.lib.Constants.DOT_GIT;
+import static org.eclipse.jgit.lib.Constants.GITDIR_FILE;
import static org.eclipse.jgit.lib.Constants.GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY;
import static org.eclipse.jgit.lib.Constants.GIT_CEILING_DIRECTORIES_KEY;
+import static org.eclipse.jgit.lib.Constants.GIT_COMMON_DIR_KEY;
import static org.eclipse.jgit.lib.Constants.GIT_DIR_KEY;
import static org.eclipse.jgit.lib.Constants.GIT_INDEX_FILE_KEY;
import static org.eclipse.jgit.lib.Constants.GIT_OBJECT_DIRECTORY_KEY;
import static org.eclipse.jgit.lib.Constants.GIT_WORK_TREE_KEY;
+import static org.eclipse.jgit.lib.Constants.OBJECTS;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
+import java.util.ArrayList;
import java.util.Collection;
-import java.util.LinkedList;
import java.util.List;
import org.eclipse.jgit.annotations.NonNull;
@@ -70,7 +74,21 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
&& ref[7] == ' ';
}
- private static File getSymRef(File workTree, File dotGit, FS fs)
+ /**
+ * Read symbolic reference file
+ *
+ * @param workTree
+ * the work tree path
+ * @param dotGit
+ * the .git file
+ * @param fs
+ * th FS util
+ * @return the file read from symbolic reference file
+ * @throws java.io.IOException
+ * the dotGit file is invalid reference
+ * @since 7.0
+ */
+ static File getSymRef(File workTree, File dotGit, FS fs)
throws IOException {
byte[] content = IO.readFully(dotGit);
if (!isSymRef(content)) {
@@ -102,6 +120,8 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
private File gitDir;
+ private File gitCommonDir;
+
private File objectDirectory;
private List<File> alternateObjectDirectories;
@@ -172,6 +192,30 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
}
/**
+ * Set common dir.
+ *
+ * @param gitCommonDir
+ * {@code GIT_COMMON_DIR}, the common repository meta directory.
+ * @return {@code this} (for chaining calls).
+ * @since 7.0
+ */
+ public B setGitCommonDir(File gitCommonDir) {
+ this.gitCommonDir = gitCommonDir;
+ this.config = null;
+ return self();
+ }
+
+ /**
+ * Get common dir.
+ *
+ * @return common dir; null if not set.
+ * @since 7.0
+ */
+ public File getGitCommonDir() {
+ return gitCommonDir;
+ }
+
+ /**
* Set the directory storing the repository's objects.
*
* @param objectDirectory
@@ -205,8 +249,9 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
*/
public B addAlternateObjectDirectory(File other) {
if (other != null) {
- if (alternateObjectDirectories == null)
- alternateObjectDirectories = new LinkedList<>();
+ if (alternateObjectDirectories == null) {
+ alternateObjectDirectories = new ArrayList<>();
+ }
alternateObjectDirectories.add(other);
}
return self();
@@ -395,9 +440,9 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
* Read standard Git environment variables and configure from those.
* <p>
* This method tries to read the standard Git environment variables, such as
- * {@code GIT_DIR} and {@code GIT_WORK_TREE} to configure this builder
- * instance. If an environment variable is set, it overrides the value
- * already set in this builder.
+ * {@code GIT_DIR}, {@code GIT_COMMON_DIR}, {@code GIT_WORK_TREE} etc. to
+ * configure this builder instance. If an environment variable is set, it
+ * overrides the value already set in this builder.
*
* @return {@code this} (for chaining calls).
*/
@@ -409,9 +454,9 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
* Read standard Git environment variables and configure from those.
* <p>
* This method tries to read the standard Git environment variables, such as
- * {@code GIT_DIR} and {@code GIT_WORK_TREE} to configure this builder
- * instance. If a property is already set in the builder, the environment
- * variable is not used.
+ * {@code GIT_DIR}, {@code GIT_COMMON_DIR}, {@code GIT_WORK_TREE} etc. to
+ * configure this builder instance. If a property is already set in the
+ * builder, the environment variable is not used.
*
* @param sr
* the SystemReader abstraction to access the environment.
@@ -424,6 +469,13 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
setGitDir(new File(val));
}
+ if (getGitCommonDir() == null) {
+ String val = sr.getenv(GIT_COMMON_DIR_KEY);
+ if (val != null) {
+ setGitCommonDir(new File(val));
+ }
+ }
+
if (getObjectDirectory() == null) {
String val = sr.getenv(GIT_OBJECT_DIRECTORY_KEY);
if (val != null)
@@ -433,7 +485,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
if (getAlternateObjectDirectories() == null) {
String val = sr.getenv(GIT_ALTERNATE_OBJECT_DIRECTORIES_KEY);
if (val != null) {
- for (String path : val.split(File.pathSeparator))
+ for (String path : val.split(File.pathSeparator, -1))
addAlternateObjectDirectory(new File(path));
}
}
@@ -453,7 +505,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
if (ceilingDirectories == null) {
String val = sr.getenv(GIT_CEILING_DIRECTORIES_KEY);
if (val != null) {
- for (String path : val.split(File.pathSeparator))
+ for (String path : val.split(File.pathSeparator, -1))
addCeilingDirectory(new File(path));
}
}
@@ -473,8 +525,9 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
*/
public B addCeilingDirectory(File root) {
if (root != null) {
- if (ceilingDirectories == null)
- ceilingDirectories = new LinkedList<>();
+ if (ceilingDirectories == null) {
+ ceilingDirectories = new ArrayList<>();
+ }
ceilingDirectories.add(root);
}
return self();
@@ -599,6 +652,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
public B setup() throws IllegalArgumentException, IOException {
requireGitDirOrWorkTree();
setupGitDir();
+ setupCommonDir();
setupWorkTree();
setupInternals();
return self();
@@ -656,6 +710,20 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
}
/**
+ * Perform standard common dir initialization.
+ *
+ * @throws java.io.IOException
+ * the repository could not be accessed
+ * @since 7.0
+ */
+ protected void setupCommonDir() throws IOException {
+ // no gitCommonDir? Try to get it from gitDir
+ if (getGitCommonDir() == null) {
+ setGitCommonDir(safeFS().getCommonDir(getGitDir()));
+ }
+ }
+
+ /**
* Perform standard work-tree initialization.
* <p>
* This is a method typically invoked inside of {@link #setup()}, near the
@@ -693,8 +761,12 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
* the repository could not be accessed
*/
protected void setupInternals() throws IOException {
- if (getObjectDirectory() == null && getGitDir() != null)
- setObjectDirectory(safeFS().resolve(getGitDir(), Constants.OBJECTS));
+ if (getObjectDirectory() == null) {
+ File commonDir = getGitCommonDir();
+ if (commonDir != null) {
+ setObjectDirectory(safeFS().resolve(commonDir, OBJECTS));
+ }
+ }
}
/**
@@ -721,12 +793,13 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
* the configuration is not available.
*/
protected Config loadConfig() throws IOException {
- if (getGitDir() != null) {
+ File commonDir = getGitCommonDir();
+ if (commonDir != null) {
// We only want the repository's configuration file, and not
// the user file, as these parameters must be unique to this
// repository and not inherited from other files.
//
- File path = safeFS().resolve(getGitDir(), Constants.CONFIG);
+ File path = safeFS().resolve(commonDir, CONFIG);
FileBasedConfig cfg = new FileBasedConfig(path, safeFS());
try {
cfg.load();
@@ -747,8 +820,29 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
//
String path = cfg.getString(CONFIG_CORE_SECTION, null,
CONFIG_KEY_WORKTREE);
- if (path != null)
+ if (path != null) {
return safeFS().resolve(getGitDir(), path).getCanonicalFile();
+ }
+
+ /*
+ * We are in worktree's $GIT_DIR folder
+ * ".git/worktrees/&lt;worktree-name&gt;" and want to get the working
+ * tree (checkout) path; so here we have an opposite link in file
+ * "gitdir" showing to the ".git" file located in the working tree read
+ * it and convert it to absolute path if it's relative
+ */
+ File gitDirFile = new File(getGitDir(), GITDIR_FILE);
+ if (gitDirFile.isFile()) {
+ String workDirPath = new String(IO.readFully(gitDirFile)).trim();
+ File workTreeDotGitFile = new File(workDirPath);
+ if (!workTreeDotGitFile.isAbsolute()) {
+ workTreeDotGitFile = new File(getGitDir(), workDirPath)
+ .getCanonicalFile();
+ }
+ if (workTreeDotGitFile != null) {
+ return workTreeDotGitFile.getParentFile();
+ }
+ }
// If core.bare is set, honor its value. Assume workTree is
// the parent directory of the repository.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
index d2367cc3d5..f9952501c3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
@@ -716,7 +716,6 @@ public class BatchRefUpdate {
: isForceRefLog();
}
- /** {@inheritDoc} */
@Override
public String toString() {
StringBuilder r = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
index f826057370..8e92f20cb0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchingProgressMonitor.java
@@ -47,13 +47,11 @@ public abstract class BatchingProgressMonitor implements ProgressMonitor {
delayStartUnit = unit;
}
- /** {@inheritDoc} */
@Override
public void start(int totalTasks) {
// Ignore the number of tasks.
}
- /** {@inheritDoc} */
@Override
public void beginTask(String title, int work) {
endTask();
@@ -62,14 +60,12 @@ public abstract class BatchingProgressMonitor implements ProgressMonitor {
task.delay(delayStartTime, delayStartUnit);
}
- /** {@inheritDoc} */
@Override
public void update(int completed) {
if (task != null)
task.update(this, completed);
}
- /** {@inheritDoc} */
@Override
public void endTask() {
if (task != null) {
@@ -78,7 +74,6 @@ public abstract class BatchingProgressMonitor implements ProgressMonitor {
}
}
- /** {@inheritDoc} */
@Override
public boolean isCancelled() {
return false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java
index b11b230a8f..acaa6335d4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BitmapIndex.java
@@ -40,6 +40,58 @@ public interface BitmapIndex {
BitmapBuilder newBitmapBuilder();
/**
+ * Report the results of {@link #getBitmap(AnyObjectId)}
+ *
+ * @since 6.8
+ */
+ interface BitmapLookupListener {
+
+ /**
+ * This object has a bitmap in the index
+ *
+ * @param oid
+ * object id
+ */
+ void onBitmapFound(AnyObjectId oid);
+
+ /**
+ * This object does not have a bitmap in the index
+ *
+ * @param oid
+ * object id
+ */
+ void onBitmapNotFound(AnyObjectId oid);
+
+ /**
+ * No-op instance
+ */
+ BitmapLookupListener NOOP = new BitmapLookupListener() {
+ @Override
+ public void onBitmapFound(AnyObjectId oid) {
+ // Nothing to do
+ }
+
+ @Override
+ public void onBitmapNotFound(AnyObjectId oid) {
+ // Nothing to do
+ }
+ };
+ }
+
+ /**
+ * Report to this listener whether {@link #getBitmap(AnyObjectId)} finds a
+ * commit.
+ *
+ * @param listener
+ * instance listening to lookup events in the index. Never null.
+ * Set to {@link BitmapLookupListener#NOOP} to disable.
+ * @since 6.8
+ */
+ default void addBitmapLookupListener(BitmapLookupListener listener) {
+ // Empty implementation for API compatibility
+ }
+
+ /**
* A bitmap representation of ObjectIds that can be iterated to return the
* underlying {@code ObjectId}s or operated on with other {@code Bitmap}s.
*/
@@ -159,7 +211,11 @@ public interface BitmapIndex {
@Override
BitmapBuilder xor(Bitmap other);
- /** @return the fully built immutable bitmap */
+ /**
+ * Build the bitmap
+ *
+ * @return the fully built immutable bitmap
+ */
Bitmap build();
/**
@@ -174,7 +230,11 @@ public interface BitmapIndex {
*/
boolean removeAllOrNone(PackBitmapIndex bitmapIndex);
- /** @return the number of elements in the bitmap. */
+ /**
+ * Get number of elements in the bitmap
+ *
+ * @return the number of elements in the bitmap.
+ */
int cardinality();
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java
index e15c7af932..7921052aaa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java
@@ -187,8 +187,7 @@ public class BranchConfig {
* @since 4.5
*/
public BranchRebaseMode getRebaseMode() {
- return config.getEnum(BranchRebaseMode.values(),
- ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
+ return config.getEnum(ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.NONE);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchTrackingStatus.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchTrackingStatus.java
index 6c625bb73a..39fc566034 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchTrackingStatus.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchTrackingStatus.java
@@ -33,6 +33,7 @@ public class BranchTrackingStatus {
* the local branch
* @return the tracking status, or null if it is not known
* @throws java.io.IOException
+ * if an IO error occurred
*/
public static BranchTrackingStatus of(Repository repository, String branchName)
throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java
index 1665f051e9..ad3c2c091d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java
@@ -194,19 +194,6 @@ public class CommitBuilder extends ObjectBuilder {
}
}
- /**
- * Set the encoding for the commit information.
- *
- * @param encodingName
- * the encoding name. See
- * {@link java.nio.charset.Charset#forName(String)}.
- * @deprecated use {@link #setEncoding(Charset)} instead.
- */
- @Deprecated
- public void setEncoding(String encodingName) {
- setEncoding(Charset.forName(encodingName));
- }
-
@Override
public byte[] build() throws UnsupportedEncodingException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
@@ -275,7 +262,6 @@ public class CommitBuilder extends ObjectBuilder {
return build();
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java
index 6a9b45b065..b1ba5dfa28 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java
@@ -119,7 +119,7 @@ public class CommitConfig {
if (!StringUtils.isEmptyOrNull(comment)) {
if ("auto".equalsIgnoreCase(comment)) { //$NON-NLS-1$
autoCommentChar = true;
- } else {
+ } else if (comment != null) {
char first = comment.charAt(0);
if (first > ' ' && first < 127) {
commentCharacter = first;
@@ -403,7 +403,7 @@ public class CommitConfig {
for (int i = 0; i < len; i++) {
char ch = line.charAt(i);
if (!Character.isWhitespace(ch)) {
- if (ch >= 0 && ch < inUse.length) {
+ if (ch < inUse.length) {
inUse[ch] = true;
}
break;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
index d1d66d280e..345cb22f80 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
@@ -30,9 +30,11 @@ import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.events.ConfigChangedEvent;
import org.eclipse.jgit.events.ConfigChangedListener;
@@ -253,9 +255,8 @@ public class Config {
* 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 typedGetter.getInt(this, section, null, name, defaultValue);
+ public int getInt(String section, String name, int defaultValue) {
+ return getInt(section, null, name, defaultValue);
}
/**
@@ -263,6 +264,23 @@ public class Config {
*
* @param section
* section the key is grouped within.
+ * @param name
+ * name of the key to get.
+ * @return an integer value from the configuration, or {@code null} if not
+ * set.
+ * @since 7.2
+ */
+ @Nullable
+ public Integer getInt(String section, String name) {
+ return getInt(section, null, name);
+ }
+
+
+ /**
+ * 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
@@ -271,10 +289,30 @@ public class Config {
* 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) {
+ public int getInt(String section, String subsection, String name,
+ int defaultValue) {
+ Integer v = typedGetter.getInt(this, section, subsection, name,
+ Integer.valueOf(defaultValue));
+ return v == null ? defaultValue : v.intValue();
+ }
+
+ /**
+ * 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.
+ * @return an integer value from the configuration, or {@code null} if not
+ * set.
+ * @since 7.2
+ */
+ @Nullable
+ public Integer getInt(String section, String subsection, String name) {
return typedGetter.getInt(this, section, subsection, name,
- defaultValue);
+ null);
}
/**
@@ -296,8 +334,30 @@ public class Config {
*/
public int getIntInRange(String section, String name, int minValue,
int maxValue, int defaultValue) {
- return typedGetter.getIntInRange(this, section, null, name, minValue,
- maxValue, defaultValue);
+ return getIntInRange(section, null, name,
+ minValue, maxValue, defaultValue);
+ }
+
+ /**
+ * Obtain an integer value from the configuration which must be inside given
+ * range.
+ *
+ * @param section
+ * section the key is grouped within.
+ * @param name
+ * name of the key to get.
+ * @param minValue
+ * minimum value
+ * @param maxValue
+ * maximum value
+ * @return an integer value from the configuration, or {@code null} if not
+ * set.
+ * @since 7.2
+ */
+ @Nullable
+ public Integer getIntInRange(String section, String name, int minValue,
+ int maxValue) {
+ return getIntInRange(section, null, name, minValue, maxValue);
}
/**
@@ -321,8 +381,34 @@ public class Config {
*/
public int getIntInRange(String section, String subsection, String name,
int minValue, int maxValue, int defaultValue) {
+ Integer v = typedGetter.getIntInRange(this, section, subsection, name,
+ minValue, maxValue, Integer.valueOf(defaultValue));
+ return v == null ? defaultValue : v.intValue();
+ }
+
+ /**
+ * Obtain an integer value from the configuration which must be inside given
+ * range.
+ *
+ * @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 minValue
+ * minimum value
+ * @param maxValue
+ * maximum value
+ * @return an integer value from the configuration, or {@code null} if not
+ * set.
+ * @since 7.2
+ */
+ @Nullable
+ public Integer getIntInRange(String section, String subsection, String name,
+ int minValue, int maxValue) {
return typedGetter.getIntInRange(this, section, subsection, name,
- minValue, maxValue, defaultValue);
+ minValue, maxValue, null);
}
/**
@@ -337,7 +423,23 @@ public class Config {
* @return an integer value from the configuration, or defaultValue.
*/
public long getLong(String section, String name, long defaultValue) {
- return typedGetter.getLong(this, section, null, name, defaultValue);
+ return getLong(section, null, name, defaultValue);
+ }
+
+ /**
+ * Obtain an integer value from the configuration.
+ *
+ * @param section
+ * section the key is grouped within.
+ * @param name
+ * name of the key to get.
+ * @return an integer value from the configuration, or {@code null} if not
+ * set.
+ * @since 7.2
+ */
+ @Nullable
+ public Long getLong(String section, String name) {
+ return getLong(section, null, name);
}
/**
@@ -354,9 +456,28 @@ public class Config {
* @return an integer value from the configuration, or defaultValue.
*/
public long getLong(final String section, String subsection,
- final String name, final long defaultValue) {
- return typedGetter.getLong(this, section, subsection, name,
- defaultValue);
+ String name, long defaultValue) {
+ Long v = typedGetter.getLong(this, section, subsection, name,
+ Long.valueOf(defaultValue));
+ return v == null ? defaultValue : v.longValue();
+ }
+
+ /**
+ * 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.
+ * @return an integer value from the configuration, or {@code null} if not
+ * set.
+ * @since 7.2
+ */
+ @Nullable
+ public Long getLong(String section, String subsection, String name) {
+ return typedGetter.getLong(this, section, subsection, name, null);
}
/**
@@ -371,9 +492,26 @@ public class Config {
* @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 typedGetter.getBoolean(this, section, null, name, defaultValue);
+ public boolean getBoolean(String section, String name,
+ boolean defaultValue) {
+ Boolean v = typedGetter.getBoolean(this, section, null, name,
+ Boolean.valueOf(defaultValue));
+ return v == null ? defaultValue : v.booleanValue();
+ }
+
+ /**
+ * Get a boolean value from the git config
+ *
+ * @param section
+ * section the key is grouped within.
+ * @param name
+ * name of the key to get.
+ * @return configured boolean value, or {@code null} if not set.
+ * @since 7.2
+ */
+ @Nullable
+ public Boolean getBoolean(String section, String name) {
+ return getBoolean(section, null, name);
}
/**
@@ -390,15 +528,35 @@ public class Config {
* @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) {
- return typedGetter.getBoolean(this, section, subsection, name,
- defaultValue);
+ public boolean getBoolean(String section, String subsection, String name,
+ boolean defaultValue) {
+ Boolean v = typedGetter.getBoolean(this, section, subsection, name,
+ Boolean.valueOf(defaultValue));
+ return v == null ? defaultValue : v.booleanValue();
+ }
+
+ /**
+ * 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.
+ * @return configured boolean value, or {@code null} if not set.
+ * @since 7.2
+ */
+ @Nullable
+ public Boolean getBoolean(String section, String subsection, String name) {
+ return typedGetter.getBoolean(this, section, subsection, name, null);
}
/**
* Parse an enumeration from the configuration.
*
+ * @param <T>
+ * type of the returned enum
* @param section
* section the key is grouped within.
* @param subsection
@@ -409,8 +567,8 @@ public class Config {
* default value to return if no value was present.
* @return the selected enumeration value, or {@code defaultValue}.
*/
- public <T extends Enum<?>> T getEnum(final String section,
- final String subsection, final String name, final T defaultValue) {
+ public <T extends Enum<?>> T getEnum(String section, String subsection,
+ String name, @NonNull T defaultValue) {
final T[] all = allValuesOf(defaultValue);
return typedGetter.getEnum(this, all, section, subsection, name,
defaultValue);
@@ -431,6 +589,8 @@ public class Config {
/**
* Parse an enumeration from the configuration.
*
+ * @param <T>
+ * type of the returned enum
* @param all
* all possible values in the enumeration which should be
* recognized. Typically {@code EnumType.values()}.
@@ -443,14 +603,41 @@ public class Config {
* @param defaultValue
* default value to return if no value was present.
* @return the selected enumeration value, or {@code defaultValue}.
+ * @deprecated use {@link #getEnum(String, String, String, Enum)} or
+ * {{@link #getEnum(Enum[], String, String, String)}} instead.
*/
- public <T extends Enum<?>> T getEnum(final T[] all, final String section,
- final String subsection, final String name, final T defaultValue) {
+ @Nullable
+ @Deprecated
+ public <T extends Enum<?>> T getEnum(T[] all, String section,
+ String subsection, String name, @Nullable T defaultValue) {
return typedGetter.getEnum(this, all, section, subsection, name,
defaultValue);
}
/**
+ * Parse an enumeration from the configuration.
+ *
+ * @param <T>
+ * type of the returned enum
+ * @param all
+ * all possible values in the enumeration which should be
+ * recognized. Typically {@code EnumType.values()}.
+ * @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.
+ * @return the selected enumeration value, or {@code null} if not set.
+ * @since 7.2
+ */
+ @Nullable
+ public <T extends Enum<?>> T getEnum(T[] all, String section,
+ String subsection, String name) {
+ return typedGetter.getEnum(this, all, section, subsection, name, null);
+ }
+
+ /**
* Get string value or null if not found.
*
* @param section
@@ -461,8 +648,8 @@ public class Config {
* the key name
* @return a String value from the config, <code>null</code> if not found
*/
- public String getString(final String section, String subsection,
- final String name) {
+ @Nullable
+ public String getString(String section, String subsection, String name) {
return getRawString(section, subsection, name);
}
@@ -521,8 +708,34 @@ public class Config {
*/
public long getTimeUnit(String section, String subsection, String name,
long defaultValue, TimeUnit wantUnit) {
+ Long v = typedGetter.getTimeUnit(this, section, subsection, name,
+ Long.valueOf(defaultValue), wantUnit);
+ return v == null ? defaultValue : v.longValue();
+
+ }
+
+ /**
+ * Parse a numerical time unit, such as "1 minute", from the configuration.
+ *
+ * @param section
+ * section the key is in.
+ * @param subsection
+ * subsection the key is in, or null if not in a subsection.
+ * @param name
+ * the key name.
+ * @param wantUnit
+ * the units of {@code defaultValue} and the return value, as
+ * well as the units to assume if the value does not contain an
+ * indication of the units.
+ * @return the value, or {@code null} if not set, expressed in
+ * {@code units}.
+ * @since 7.2
+ */
+ @Nullable
+ public Long getTimeUnit(String section, String subsection, String name,
+ TimeUnit wantUnit) {
return typedGetter.getTimeUnit(this, section, subsection, name,
- defaultValue, wantUnit);
+ null, wantUnit);
}
/**
@@ -550,8 +763,9 @@ public class Config {
* @return the {@link Path}, or {@code defaultValue} if not set
* @since 5.10
*/
+ @Nullable
public Path getPath(String section, String subsection, String name,
- @NonNull FS fs, File resolveAgainst, Path defaultValue) {
+ @NonNull FS fs, File resolveAgainst, @Nullable Path defaultValue) {
return typedGetter.getPath(this, section, subsection, name, fs,
resolveAgainst, defaultValue);
}
@@ -722,7 +936,7 @@ public class Config {
* responsible for issuing {@link #fireConfigChangedEvent()} calls
* themselves.
*
- * @return <code></code>
+ * @return whether to issue change events for transient changes
*/
protected boolean notifyUponTransientChanges() {
return true;
@@ -735,7 +949,7 @@ public class Config {
listeners.dispatch(new ConfigChangedEvent());
}
- String getRawString(final String section, final String subsection,
+ private String getRawString(final String section, final String subsection,
final String name) {
String[] lst = getRawStringList(section, subsection, name);
if (lst != null) {
@@ -847,6 +1061,8 @@ public class Config {
* name = value
* </pre>
*
+ * @param <T>
+ * type of the enum to set
* @param section
* section name, e.g "branch"
* @param subsection
@@ -915,29 +1131,52 @@ public class Config {
* optional subsection value, e.g. a branch name
*/
public void unsetSection(String section, String subsection) {
+ removeSection(section, subsection);
+ }
+
+ /**
+ * Removes all configuration values under a single section.
+ *
+ * @param section
+ * section name, e.g "branch"
+ * @param subsection
+ * optional subsection value, e.g. a branch name
+ * @return {@code true} if a section was present and was removed;
+ * {@code false} if the config was not changed (i.e., no such
+ * section was present)
+ * @since 6.8
+ */
+ public boolean removeSection(String section, String subsection) {
ConfigSnapshot src, res;
+ AtomicBoolean changed = new AtomicBoolean();
do {
src = state.get();
- res = unsetSection(src, section, subsection);
+ changed.set(false);
+ res = unsetSection(src, section, subsection, changed);
} while (!state.compareAndSet(src, res));
+ return changed.get();
}
- private ConfigSnapshot unsetSection(final ConfigSnapshot srcState,
- final String section,
- final String subsection) {
+ private ConfigSnapshot unsetSection(ConfigSnapshot srcState, String section,
+ String subsection, AtomicBoolean changed) {
final int max = srcState.entryList.size();
final ArrayList<ConfigLine> r = new ArrayList<>(max);
boolean lastWasMatch = false;
for (ConfigLine e : srcState.entryList) {
- if (e.includedFrom == null && e.match(section, subsection)) {
- // Skip this record, it's for the section we are removing.
- lastWasMatch = true;
+ if (e.includedFrom != null) {
+ r.add(e);
continue;
}
-
- if (lastWasMatch && e.section == null && e.subsection == null)
+ if (lastWasMatch && e.section == null && e.subsection == null) {
continue; // skip this padding line in the section.
+ }
+ lastWasMatch = e.match(section, subsection);
+ if (lastWasMatch) {
+ // Skip this record, it's for the section we are removing.
+ changed.set(true);
+ continue;
+ }
r.add(e);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index c4b6bf955e..c4550329d3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -60,7 +60,8 @@ public final class ConfigConstants {
public static final String CONFIG_KEY_PROMPT = "prompt";
/**
- * The "trustExitCode" key within "difftool" or "mergetool.<name>." section
+ * The "trustExitCode" key within "difftool" or "mergetool.&lt;name&gt;."
+ * section
*
* @since 6.1
*/
@@ -77,6 +78,13 @@ public final class ConfigConstants {
public static final String CONFIG_DFS_SECTION = "dfs";
/**
+ * The dfs cache subsection prefix.
+ *
+ * @since 7.0
+ */
+ public static final String CONFIG_DFS_CACHE_PREFIX = "dfs.";
+
+ /**
* The "receive" section
* @since 4.6
*/
@@ -103,11 +111,18 @@ public final class ConfigConstants {
/** The "gc" section */
public static final String CONFIG_GC_SECTION = "gc";
+ /**
+ * The "repack" section
+ * @since 5.13.3
+ */
+ public static final String CONFIG_REPACK_SECTION = "repack";
+
/** The "pack" section */
public static final String CONFIG_PACK_SECTION = "pack";
/**
* The "fetch" section
+ *
* @since 3.3
*/
public static final String CONFIG_FETCH_SECTION = "fetch";
@@ -191,7 +206,36 @@ public final class ConfigConstants {
public static final String CONFIG_KEY_SIGNINGKEY = "signingKey";
/**
+ * The "ssh" subsection key.
+ *
+ * @since 7.1
+ */
+ public static final String CONFIG_SSH_SUBSECTION = "ssh";
+
+ /**
+ * The "defaultKeyCommand" key.
+ *
+ * @since 7.1
+ */
+ public static final String CONFIG_KEY_SSH_DEFAULT_KEY_COMMAND = "defaultKeyCommand";
+
+ /**
+ * The "allowedSignersFile" key.
+ *
+ * @since 7.1
+ */
+ public static final String CONFIG_KEY_SSH_ALLOWED_SIGNERS_FILE = "allowedSignersFile";
+
+ /**
+ * The "revocationFile" key,
+ *
+ * @since 7.1
+ */
+ public static final String CONFIG_KEY_SSH_REVOCATION_FILE = "revocationFile";
+
+ /**
* The "commit" section
+ *
* @since 5.2
*/
public static final String CONFIG_COMMIT_SECTION = "commit";
@@ -324,13 +368,24 @@ public final class ConfigConstants {
public static final String CONFIG_KEY_DELTA_BASE_CACHE_LIMIT = "deltaBaseCacheLimit";
/**
+ * The "packExtensions" key
+ *
+ * @since 7.0
+ **/
+ public static final String CONFIG_KEY_PACK_EXTENSIONS = "packExtensions";
+
+ /**
* The "symlinks" key
* @since 3.3
*/
public static final String CONFIG_KEY_SYMLINKS = "symlinks";
- /** The "streamFileThreshold" key */
- public static final String CONFIG_KEY_STREAM_FILE_TRESHOLD = "streamFileThreshold";
+ /**
+ * The "streamFileThreshold" key
+ *
+ * @since 6.8
+ */
+ public static final String CONFIG_KEY_STREAM_FILE_THRESHOLD = "streamFileThreshold";
/**
* The "packedGitMmap" key
@@ -362,6 +417,12 @@ public final class ConfigConstants {
*/
public static final String CONFIG_KEY_PACKED_GIT_USE_STRONGREFS = "packedgitusestrongrefs";
+ /**
+ * The "packedIndexGitUseStrongRefs" key
+ * @since 6.7
+ */
+ public static final String CONFIG_KEY_PACKED_INDEX_GIT_USE_STRONGREFS = "packedindexgitusestrongrefs";
+
/** The "remote" key */
public static final String CONFIG_KEY_REMOTE = "remote";
@@ -385,6 +446,13 @@ public final class ConfigConstants {
/** The "rebase" key */
public static final String CONFIG_KEY_REBASE = "rebase";
+ /**
+ * The "checkout" key
+ *
+ * @since 7.2
+ */
+ public static final String CONFIG_KEY_CHECKOUT = "checkout";
+
/** The "url" key */
public static final String CONFIG_KEY_URL = "url";
@@ -532,11 +600,21 @@ public final class ConfigConstants {
/**
* The "trustfolderstat" key in the "core" section
+ *
* @since 3.6
+ * @deprecated use {CONFIG_KEY_TRUST_STAT} instead
*/
+ @Deprecated(since = "7.2", forRemoval = true)
public static final String CONFIG_KEY_TRUSTFOLDERSTAT = "trustfolderstat";
/**
+ * The "trustfilestat" key in the "core"section
+ *
+ * @since 7.2
+ */
+ public static final String CONFIG_KEY_TRUST_STAT = "truststat";
+
+ /**
* The "supportsAtomicFileCreation" key in the "core" section
*
* @since 4.5
@@ -727,6 +805,13 @@ public final class ConfigConstants {
public static final String CONFIG_KEY_BITMAP_EXCESSIVE_BRANCH_COUNT = "bitmapexcessivebranchcount";
/**
+ * The "pack.bitmapExcessiveBranchTipCount" key
+ *
+ * @since 6.9
+ */
+ public static final String CONFIG_KEY_BITMAP_EXCESSIVE_BRANCH_TIP_COUNT = "bitmapexcessivebranchtipcount";
+
+ /**
* The "pack.bitmapExcludedRefsPrefixes" key
* @since 5.13.2
*/
@@ -745,6 +830,13 @@ public final class ConfigConstants {
public static final String CONFIG_KEY_BITMAP_RECENT_COMMIT_COUNT = "bitmaprecentcommitspan";
/**
+ * The "pack.writeReverseIndex" key
+ *
+ * @since 6.6
+ */
+ public static final String CONFIG_KEY_WRITE_REVERSE_INDEX = "writeReverseIndex";
+
+ /**
* The "pack.buildBitmaps" key
* @since 5.8
*/
@@ -836,6 +928,13 @@ public final class ConfigConstants {
public static final String CONFIG_KEY_MIN_BYTES_OBJ_SIZE_INDEX = "minBytesForObjSizeIndex";
/**
+ * The "repack.packKeptObjects" key
+ *
+ * @since 5.13.3
+ */
+ public static final String CONFIG_KEY_PACK_KEPT_OBJECTS = "packkeptobjects";
+
+ /**
* The "feature" section
*
* @since 5.9
@@ -927,6 +1026,34 @@ public final class ConfigConstants {
public static final String CONFIG_KEY_TRUST_PACKED_REFS_STAT = "trustPackedRefsStat";
/**
+ * The "trustLooseRefStat" key
+ *
+ * @since 6.9
+ */
+ public static final String CONFIG_KEY_TRUST_LOOSE_REF_STAT = "trustLooseRefStat";
+
+ /**
+ * The "trustLooseRefStat" key
+ *
+ * @since 7.2
+ */
+ public static final String CONFIG_KEY_TRUST_PACK_STAT = "trustPackStat";
+
+ /**
+ * The "trustLooseObjectFileStat" key
+ *
+ * @since 7.2
+ */
+ public static final String CONFIG_KEY_TRUST_LOOSE_OBJECT_STAT = "trustLooseObjectStat";
+
+ /**
+ * The "trustTablesListStat" key
+ *
+ * @since 7.2
+ */
+ public static final String CONFIG_KEY_TRUST_TABLESLIST_STAT = "trustTablesListStat";
+
+ /**
* The "pack.preserveOldPacks" key
*
* @since 5.13.2
@@ -939,4 +1066,53 @@ public final class ConfigConstants {
* @since 5.13.2
*/
public static final String CONFIG_KEY_PRUNE_PRESERVED = "prunepreserved";
+
+ /**
+ * The "commitGraph" section
+ *
+ * @since 6.7
+ */
+ public static final String CONFIG_COMMIT_GRAPH_SECTION = "commitGraph";
+
+ /**
+ * The "writeChangedPaths" key
+ *
+ * @since 6.7
+ */
+ public static final String CONFIG_KEY_WRITE_CHANGED_PATHS = "writeChangedPaths";
+
+ /**
+ * The "readChangedPaths" key
+ *
+ * @since 6.7
+ */
+ public static final String CONFIG_KEY_READ_CHANGED_PATHS = "readChangedPaths";
+
+ /**
+ * The "useObjectSizeIndex" key
+ *
+ * @since 7.0
+ */
+ public static final String CONFIG_KEY_USE_OBJECT_SIZE_INDEX = "useObjectSizeIndex";
+
+ /**
+ * The "loadRevIndexInParallel" key
+ *
+ * @since 7.1
+ */
+ public static final String CONFIG_KEY_LOAD_REV_INDEX_IN_PARALLEL = "loadRevIndexInParallel";
+
+ /**
+ * The "reftable" section
+ *
+ * @since 7.2
+ */
+ public static final String CONFIG_REFTABLE_SECTION = "reftable";
+
+ /**
+ * The "autorefresh" key
+ *
+ * @since 7.2
+ */
+ public static final String CONFIG_KEY_AUTOREFRESH = "autorefresh";
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java
index a9235ebcdf..e9a8b44271 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigLine.java
@@ -83,7 +83,6 @@ class ConfigLine {
return a.equals(b);
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
index 0b8bf8c6c5..9de8392690 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -12,10 +12,13 @@
package org.eclipse.jgit.lib;
+import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.nio.charset.StandardCharsets.UTF_8;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CodingErrorAction;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
@@ -203,24 +206,6 @@ public final class Constants {
*/
public static final byte[] PACK_SIGNATURE = { 'P', 'A', 'C', 'K' };
- /**
- * Native character encoding for commit messages, file names...
- *
- * @deprecated Use {@link java.nio.charset.StandardCharsets#UTF_8} directly
- * instead.
- */
- @Deprecated
- public static final Charset CHARSET;
-
- /**
- * Native character encoding for commit messages, file names...
- *
- * @deprecated Use {@link java.nio.charset.StandardCharsets#UTF_8} directly
- * instead.
- */
- @Deprecated
- public static final String CHARACTER_ENCODING;
-
/** Default main branch name */
public static final String MASTER = "master";
@@ -273,6 +258,20 @@ public final class Constants {
public static final String INFO_REFS = "info/refs";
/**
+ * Name of heads folder or file in refs.
+ *
+ * @since 7.0
+ */
+ public static final String HEADS = "heads";
+
+ /**
+ * Prefix for any log.
+ *
+ * @since 7.0
+ */
+ public static final String L_LOGS = LOGS + "/";
+
+ /**
* Info alternates file (goes under OBJECTS)
* @since 5.5
*/
@@ -337,15 +336,24 @@ public final class Constants {
public static final String GIT_CONFIG_NOSYSTEM_KEY = "GIT_CONFIG_NOSYSTEM";
/**
- * The key of the XDG_CONFIG_HOME directory defined in the XDG base
- * directory specification, see
- * {@link "https://wiki.archlinux.org/index.php/XDG_Base_Directory"}
+ * The key of the XDG_CONFIG_HOME directory defined in the
+ * <a href="https://wiki.archlinux.org/index.php/XDG_Base_Directory">
+ * XDG Base Directory specification</a>.
*
* @since 5.5.2
*/
public static final String XDG_CONFIG_HOME = "XDG_CONFIG_HOME";
/**
+ * The key of the XDG_CACHE_HOME directory defined in the
+ * <a href="https://wiki.archlinux.org/index.php/XDG_Base_Directory">
+ * XDG Base Directory specification</a>.
+ *
+ * @since 7.3
+ */
+ public static final String XDG_CACHE_HOME = "XDG_CACHE_HOME";
+
+ /**
* The environment variable that limits how close to the root of the file
* systems JGit will traverse when looking for a repository root.
*/
@@ -358,6 +366,14 @@ public final class Constants {
public static final String GIT_DIR_KEY = "GIT_DIR";
/**
+ * The environment variable that tells us which directory is the common
+ * ".git" directory.
+ *
+ * @since 7.0
+ */
+ public static final String GIT_COMMON_DIR_KEY = "GIT_COMMON_DIR";
+
+ /**
* The environment variable that tells us which directory is the working
* directory.
*/
@@ -459,6 +475,36 @@ public final class Constants {
public static final String GITDIR = "gitdir: ";
/**
+ * Name of the file (inside gitDir) that references the worktree's .git
+ * file (opposite link).
+ *
+ * .git/worktrees/&lt;worktree-name&gt;/gitdir
+ *
+ * A text file containing the absolute path back to the .git file that
+ * points here. This file is used to verify if the linked repository has been
+ * manually removed in which case this directory is no longer needed.
+ * The modification time (mtime) of this file should be updated each time
+ * the linked repository is accessed.
+ *
+ * @since 7.0
+ */
+ public static final String GITDIR_FILE = "gitdir";
+
+ /**
+ * Name of the file (inside gitDir) that has reference to $GIT_COMMON_DIR.
+ *
+ * .git/worktrees/&lt;worktree-name&gt;/commondir
+ *
+ * If this file exists, $GIT_COMMON_DIR will be set to the path specified in
+ * this file unless it is explicitly set. If the specified path is relative,
+ * it is relative to $GIT_DIR. The repository with commondir is incomplete
+ * without the repository pointed by "commondir".
+ *
+ * @since 7.0
+ */
+ public static final String COMMONDIR_FILE = "commondir";
+
+ /**
* Name of the folder (inside gitDir) where submodules are stored
*
* @since 3.6
@@ -494,6 +540,34 @@ public final class Constants {
public static final String ATTR_BUILTIN_BINARY_MERGER = "binary"; //$NON-NLS-1$
/**
+ * Prefix of a GPG signature.
+ *
+ * @since 7.0
+ */
+ public static final String GPG_SIGNATURE_PREFIX = "-----BEGIN PGP SIGNATURE-----"; //$NON-NLS-1$
+
+ /**
+ * Prefix of a CMS signature (X.509, S/MIME).
+ *
+ * @since 7.0
+ */
+ public static final String CMS_SIGNATURE_PREFIX = "-----BEGIN SIGNED MESSAGE-----"; //$NON-NLS-1$
+
+ /**
+ * Prefix of an SSH signature.
+ *
+ * @since 7.0
+ */
+ public static final String SSH_SIGNATURE_PREFIX = "-----BEGIN SSH SIGNATURE-----"; //$NON-NLS-1$
+
+ /**
+ * Union built-in merge driver
+ *
+ * @since 6.10.1
+ */
+ public static final String ATTR_BUILTIN_UNION_MERGE_DRIVER = "union"; //$NON-NLS-1$
+
+ /**
* Create a new digest function for objects.
*
* @return a new digest object.
@@ -661,44 +735,32 @@ public final class Constants {
* the 7-bit ASCII character space.
*/
public static byte[] encodeASCII(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(MessageFormat.format(JGitText.get().notASCIIString, s));
- r[k] = (byte) c;
+ try {
+ CharsetEncoder encoder = US_ASCII.newEncoder()
+ .onUnmappableCharacter(CodingErrorAction.REPORT)
+ .onMalformedInput(CodingErrorAction.REPORT);
+ return encoder.encode(CharBuffer.wrap(s)).array();
+ } catch (CharacterCodingException e) {
+ throw new IllegalArgumentException(
+ MessageFormat.format(JGitText.get().notASCIIString, s), e);
}
- return r;
}
/**
- * Convert a string to a byte array in the standard character encoding.
+ * Convert a string to a byte array in the standard character encoding UTF8.
*
* @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(String str) {
- final ByteBuffer bb = UTF_8.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;
+ return str.getBytes(UTF_8);
}
static {
if (OBJECT_ID_LENGTH != newMessageDigest().getDigestLength())
throw new LinkageError(JGitText.get().incorrectOBJECT_ID_LENGTH);
- CHARSET = UTF_8;
- CHARACTER_ENCODING = UTF_8.name();
}
/** name of the file containing the commit msg for a merge commit */
@@ -766,7 +828,7 @@ public final class Constants {
*
* @since 6.5
*/
- public static int COMMIT_GENERATION_UNKNOWN = Integer.MAX_VALUE;
+ public static final int COMMIT_GENERATION_UNKNOWN = Integer.MAX_VALUE;
/**
* If a commit-graph file was written by a version of Git that did not
@@ -775,7 +837,7 @@ public final class Constants {
*
* @since 6.5
*/
- public static int COMMIT_GENERATION_NOT_COMPUTED = 0;
+ public static final int COMMIT_GENERATION_NOT_COMPUTED = 0;
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
index 4de1801d04..0e27b2743c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
@@ -17,12 +17,16 @@ package org.eclipse.jgit.lib;
import static java.util.zip.Deflater.DEFAULT_COMPRESSION;
+import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Config.SectionParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* This class keeps git repository core parameters.
*/
public class CoreConfig {
+ private static final Logger LOG = LoggerFactory.getLogger(CoreConfig.class);
/** Key for {@link Config#get(SectionParser)}. */
public static final Config.SectionParser<CoreConfig> KEY = CoreConfig::new;
@@ -127,7 +131,9 @@ public class CoreConfig {
* Permissible values for {@code core.trustPackedRefsStat}.
*
* @since 6.1.1
+ * @deprecated use {@link TrustStat} instead
*/
+ @Deprecated(since = "7.2", forRemoval = true)
public enum TrustPackedRefsStat {
/** Do not trust file attributes of the packed-refs file. */
NEVER,
@@ -135,27 +141,82 @@ public class CoreConfig {
/** Trust file attributes of the packed-refs file. */
ALWAYS,
- /** Open and close the packed-refs file to refresh its file attributes
- * and then trust it. */
+ /**
+ * Open and close the packed-refs file to refresh its file attributes
+ * and then trust it.
+ */
AFTER_OPEN,
- /** {@code core.trustPackedRefsStat} defaults to this when it is
- * not set */
+ /**
+ * {@code core.trustPackedRefsStat} defaults to this when it is not set
+ */
UNSET
}
+ /**
+ * Permissible values for {@code core.trustLooseRefStat}.
+ *
+ * @since 6.9
+ * @deprecated use {@link TrustStat} instead
+ */
+ @Deprecated(since = "7.2", forRemoval = true)
+ public enum TrustLooseRefStat {
+
+ /** Trust file attributes of the loose ref. */
+ ALWAYS,
+
+ /**
+ * Open and close parent directories of the loose ref file until the
+ * repository root to refresh its file attributes and then trust it.
+ */
+ AFTER_OPEN,
+ }
+
+ /**
+ * Values for {@code core.trustXXX} options.
+ *
+ * @since 7.2
+ */
+ public enum TrustStat {
+ /** Do not trust file attributes of a File. */
+ NEVER,
+
+ /** Always trust file attributes of a File. */
+ ALWAYS,
+
+ /** Open and close the File to refresh its file attributes
+ * and then trust it. */
+ AFTER_OPEN,
+
+ /**
+ * Used for specific options to inherit value from value set for
+ * core.trustStat.
+ */
+ INHERIT
+ }
+
private final int compression;
private final int packIndexVersion;
- private final LogRefUpdates logAllRefUpdates;
-
private final String excludesfile;
private final String attributesfile;
private final boolean commitGraph;
+ private final TrustStat trustStat;
+
+ private final TrustStat trustPackedRefsStat;
+
+ private final TrustStat trustLooseRefStat;
+
+ private final TrustStat trustPackStat;
+
+ private final TrustStat trustLooseObjectStat;
+
+ private final TrustStat trustTablesListStat;
+
/**
* Options for symlink handling
*
@@ -185,14 +246,17 @@ public class CoreConfig {
DOTGITONLY
}
- private CoreConfig(Config rc) {
+ /**
+ * Create a new core configuration from the passed configuration.
+ *
+ * @param rc
+ * git configuration
+ */
+ CoreConfig(Config rc) {
compression = rc.getInt(ConfigConstants.CONFIG_CORE_SECTION,
ConfigConstants.CONFIG_KEY_COMPRESSION, DEFAULT_COMPRESSION);
packIndexVersion = rc.getInt(ConfigConstants.CONFIG_PACK_SECTION,
ConfigConstants.CONFIG_KEY_INDEXVERSION, 2);
- logAllRefUpdates = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
- ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES,
- LogRefUpdates.TRUE);
excludesfile = rc.getString(ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_EXCLUDESFILE);
attributesfile = rc.getString(ConfigConstants.CONFIG_CORE_SECTION,
@@ -200,6 +264,68 @@ public class CoreConfig {
commitGraph = rc.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
ConfigConstants.CONFIG_COMMIT_GRAPH,
DEFAULT_COMMIT_GRAPH_ENABLE);
+
+ trustStat = parseTrustStat(rc);
+ trustPackedRefsStat = parseTrustPackedRefsStat(rc);
+ trustLooseRefStat = parseTrustLooseRefStat(rc);
+ trustPackStat = parseTrustPackFileStat(rc);
+ trustLooseObjectStat = parseTrustLooseObjectFileStat(rc);
+ trustTablesListStat = parseTablesListStat(rc);
+ }
+
+ private static TrustStat parseTrustStat(Config rc) {
+ Boolean tfs = rc.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT);
+ TrustStat ts = rc.getEnum(TrustStat.values(),
+ ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_TRUST_STAT);
+ if (tfs != null) {
+ if (ts == null) {
+ LOG.warn(JGitText.get().deprecatedTrustFolderStat);
+ return tfs.booleanValue() ? TrustStat.ALWAYS : TrustStat.NEVER;
+ }
+ LOG.warn(JGitText.get().precedenceTrustConfig);
+ }
+ if (ts == null) {
+ ts = TrustStat.ALWAYS;
+ } else if (ts == TrustStat.INHERIT) {
+ LOG.warn(JGitText.get().invalidTrustStat);
+ ts = TrustStat.ALWAYS;
+ }
+ return ts;
+ }
+
+ private TrustStat parseTrustPackedRefsStat(Config rc) {
+ return inheritParseTrustStat(rc,
+ ConfigConstants.CONFIG_KEY_TRUST_PACKED_REFS_STAT);
+ }
+
+ private TrustStat parseTrustLooseRefStat(Config rc) {
+ return inheritParseTrustStat(rc,
+ ConfigConstants.CONFIG_KEY_TRUST_LOOSE_REF_STAT);
+ }
+
+ private TrustStat parseTrustPackFileStat(Config rc) {
+ return inheritParseTrustStat(rc,
+ ConfigConstants.CONFIG_KEY_TRUST_PACK_STAT);
+ }
+
+ private TrustStat parseTrustLooseObjectFileStat(Config rc) {
+ return inheritParseTrustStat(rc,
+ ConfigConstants.CONFIG_KEY_TRUST_LOOSE_OBJECT_STAT);
+ }
+
+ private TrustStat inheritParseTrustStat(Config rc, String key) {
+ TrustStat t = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null, key,
+ TrustStat.INHERIT);
+ return t == TrustStat.INHERIT ? trustStat : t;
+ }
+
+ private TrustStat parseTablesListStat(Config rc) {
+ TrustStat t = rc.getEnum(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_TRUST_TABLESLIST_STAT,
+ TrustStat.INHERIT);
+ return t == TrustStat.INHERIT ? trustStat : t;
}
/**
@@ -221,20 +347,6 @@ public class CoreConfig {
}
/**
- * Whether to log all refUpdates
- *
- * @return whether to log all refUpdates
- * @deprecated since 5.6; default value depends on whether the repository is
- * bare. Use
- * {@link Config#getEnum(String, String, String, Enum)}
- * directly.
- */
- @Deprecated
- public boolean isLogAllRefUpdates() {
- return !LogRefUpdates.FALSE.equals(logAllRefUpdates);
- }
-
- /**
* Get path of excludesfile
*
* @return path of excludesfile
@@ -264,4 +376,70 @@ public class CoreConfig {
public boolean enableCommitGraph() {
return commitGraph;
}
+
+ /**
+ * Get how far we can trust file attributes of packed-refs file which is
+ * used to store {@link org.eclipse.jgit.lib.Ref}s in
+ * {@link org.eclipse.jgit.internal.storage.file.RefDirectory}.
+ *
+ * @return how far we can trust file attributes of packed-refs file.
+ *
+ * @since 7.2
+ */
+ public TrustStat getTrustPackedRefsStat() {
+ return trustPackedRefsStat;
+ }
+
+ /**
+ * Get how far we can trust file attributes of loose ref files which are
+ * used to store {@link org.eclipse.jgit.lib.Ref}s in
+ * {@link org.eclipse.jgit.internal.storage.file.RefDirectory}.
+ *
+ * @return how far we can trust file attributes of loose ref files.
+ *
+ * @since 7.2
+ */
+ public TrustStat getTrustLooseRefStat() {
+ return trustLooseRefStat;
+ }
+
+ /**
+ * Get how far we can trust file attributes of packed-refs file which is
+ * used to store {@link org.eclipse.jgit.lib.Ref}s in
+ * {@link org.eclipse.jgit.internal.storage.file.RefDirectory}.
+ *
+ * @return how far we can trust file attributes of packed-refs file.
+ *
+ * @since 7.2
+ */
+ public TrustStat getTrustPackStat() {
+ return trustPackStat;
+ }
+
+ /**
+ * Get how far we can trust file attributes of loose ref files which are
+ * used to store {@link org.eclipse.jgit.lib.Ref}s in
+ * {@link org.eclipse.jgit.internal.storage.file.RefDirectory}.
+ *
+ * @return how far we can trust file attributes of loose ref files.
+ *
+ * @since 7.2
+ */
+ public TrustStat getTrustLooseObjectStat() {
+ return trustLooseObjectStat;
+ }
+
+ /**
+ * Get how far we can trust file attributes of the "tables.list" file which
+ * is used to store the list of filenames of the files storing
+ * {@link org.eclipse.jgit.internal.storage.reftable.Reftable}s in
+ * {@link org.eclipse.jgit.internal.storage.file.FileReftableDatabase}.
+ *
+ * @return how far we can trust file attributes of the "tables.list" file.
+ *
+ * @since 7.2
+ */
+ public TrustStat getTrustTablesListStat() {
+ return trustTablesListStat;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
index 86409403b0..3059f283fe 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
@@ -18,6 +18,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Config.ConfigEnum;
import org.eclipse.jgit.transport.RefSpec;
@@ -31,29 +32,37 @@ import org.eclipse.jgit.util.StringUtils;
*/
public class DefaultTypedConfigGetter implements TypedConfigGetter {
- /** {@inheritDoc} */
+ @SuppressWarnings("boxed")
@Override
public boolean getBoolean(Config config, String section, String subsection,
String name, boolean defaultValue) {
- String n = config.getRawString(section, subsection, name);
+ return neverNull(getBoolean(config, section, subsection, name,
+ Boolean.valueOf(defaultValue)));
+ }
+
+ @Nullable
+ @Override
+ public Boolean getBoolean(Config config, String section, String subsection,
+ String name, @Nullable Boolean defaultValue) {
+ String n = config.getString(section, subsection, name);
if (n == null) {
return defaultValue;
}
if (Config.isMissing(n)) {
- return true;
+ return Boolean.TRUE;
}
try {
- return StringUtils.toBoolean(n);
+ return Boolean.valueOf(StringUtils.toBoolean(n));
} catch (IllegalArgumentException err) {
throw new IllegalArgumentException(MessageFormat.format(
JGitText.get().invalidBooleanValue, section, name, n), err);
}
}
- /** {@inheritDoc} */
+ @Nullable
@Override
public <T extends Enum<?>> T getEnum(Config config, T[] all, String section,
- String subsection, String name, T defaultValue) {
+ String subsection, String name, @Nullable T defaultValue) {
String value = config.getString(section, subsection, name);
if (value == null) {
return defaultValue;
@@ -106,62 +115,102 @@ public class DefaultTypedConfigGetter implements TypedConfigGetter {
JGitText.get().enumValueNotSupported2, section, name, value));
}
- /** {@inheritDoc} */
@Override
public int getInt(Config config, String section, String subsection,
String name, int defaultValue) {
- long val = config.getLong(section, subsection, name, defaultValue);
+ return neverNull(getInt(config, section, subsection, name,
+ Integer.valueOf(defaultValue)));
+ }
+
+ @Nullable
+ @Override
+ @SuppressWarnings("boxing")
+ public Integer getInt(Config config, String section, String subsection,
+ String name, @Nullable Integer defaultValue) {
+ Long longDefault = defaultValue != null
+ ? Long.valueOf(defaultValue.longValue())
+ : null;
+ Long val = config.getLong(section, subsection, name);
+ if (val == null) {
+ val = longDefault;
+ }
+ if (val == null) {
+ return null;
+ }
if (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE) {
- return (int) val;
+ return Integer.valueOf(Math.toIntExact(val));
}
throw new IllegalArgumentException(MessageFormat
.format(JGitText.get().integerValueOutOfRange, section, name));
}
- /** {@inheritDoc} */
@Override
public int getIntInRange(Config config, String section, String subsection,
String name, int minValue, int maxValue, int defaultValue) {
- int val = getInt(config, section, subsection, name, defaultValue);
+ return neverNull(getIntInRange(config, section, subsection, name,
+ minValue, maxValue, Integer.valueOf(defaultValue)));
+ }
+
+ @Override
+ @SuppressWarnings("boxing")
+ public Integer getIntInRange(Config config, String section,
+ String subsection, String name, int minValue, int maxValue,
+ Integer defaultValue) {
+ Integer val = getInt(config, section, subsection, name, defaultValue);
+ if (val == null) {
+ return null;
+ }
if ((val >= minValue && val <= maxValue) || val == UNSET_INT) {
return val;
}
if (subsection == null) {
- throw new IllegalArgumentException(MessageFormat.format(
- JGitText.get().integerValueNotInRange, section, name,
- Integer.valueOf(val), Integer.valueOf(minValue),
- Integer.valueOf(maxValue)));
+ throw new IllegalArgumentException(
+ MessageFormat.format(JGitText.get().integerValueNotInRange,
+ section, name, val, minValue, maxValue));
}
throw new IllegalArgumentException(MessageFormat.format(
JGitText.get().integerValueNotInRangeSubSection, section,
- subsection, name, Integer.valueOf(val),
- Integer.valueOf(minValue), Integer.valueOf(maxValue)));
+ subsection, name, val, minValue, maxValue));
}
- /** {@inheritDoc} */
@Override
public long getLong(Config config, String section, String subsection,
String name, long defaultValue) {
- final String str = config.getString(section, subsection, name);
+ return neverNull(getLong(config, section, subsection, name,
+ Long.valueOf(defaultValue)));
+ }
+
+ @Nullable
+ @Override
+ public Long getLong(Config config, String section, String subsection,
+ String name, @Nullable Long defaultValue) {
+ String str = config.getString(section, subsection, name);
if (str == null) {
return defaultValue;
}
try {
- return StringUtils.parseLongWithSuffix(str, false);
+ return Long.valueOf(StringUtils.parseLongWithSuffix(str, false));
} catch (StringIndexOutOfBoundsException e) {
// Empty
return defaultValue;
} catch (NumberFormatException nfe) {
- throw new IllegalArgumentException(MessageFormat.format(
- JGitText.get().invalidIntegerValue, section, name, str),
+ throw new IllegalArgumentException(
+ MessageFormat.format(JGitText.get().invalidIntegerValue,
+ section, name, str),
nfe);
}
}
- /** {@inheritDoc} */
@Override
public long getTimeUnit(Config config, String section, String subsection,
String name, long defaultValue, TimeUnit wantUnit) {
+ return neverNull(getTimeUnit(config, section, subsection, name,
+ Long.valueOf(defaultValue), wantUnit));
+ }
+
+ @Override
+ public Long getTimeUnit(Config config, String section, String subsection,
+ String name, @Nullable Long defaultValue, TimeUnit wantUnit) {
String valueString = config.getString(section, subsection, name);
if (valueString == null) {
@@ -238,8 +287,8 @@ public class DefaultTypedConfigGetter implements TypedConfigGetter {
}
try {
- return wantUnit.convert(Long.parseLong(digits) * inputMul,
- inputUnit);
+ return Long.valueOf(wantUnit
+ .convert(Long.parseLong(digits) * inputMul, inputUnit));
} catch (NumberFormatException nfe) {
IllegalArgumentException iae = notTimeUnit(section, subsection,
unitName, valueString);
@@ -269,7 +318,6 @@ public class DefaultTypedConfigGetter implements TypedConfigGetter {
section, name, valueString));
}
- /** {@inheritDoc} */
@Override
@NonNull
public List<RefSpec> getRefSpecs(Config config, String section,
@@ -281,4 +329,14 @@ public class DefaultTypedConfigGetter implements TypedConfigGetter {
}
return result;
}
+
+ // Trick for the checkers. When we use this, one is never null, but
+ // they don't know.
+ @NonNull
+ private static <T> T neverNull(T one) {
+ if (one == null) {
+ throw new IllegalArgumentException();
+ }
+ return one;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java
index 94d28eb345..a5410b778c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/EmptyProgressMonitor.java
@@ -18,31 +18,26 @@ package org.eclipse.jgit.lib;
*/
public abstract class EmptyProgressMonitor implements ProgressMonitor {
- /** {@inheritDoc} */
@Override
public void start(int totalTasks) {
// empty
}
- /** {@inheritDoc} */
@Override
public void beginTask(String title, int totalWork) {
// empty
}
- /** {@inheritDoc} */
@Override
public void update(int completed) {
// empty
}
- /** {@inheritDoc} */
@Override
public void endTask() {
// empty
}
- /** {@inheritDoc} */
@Override
public boolean isCancelled() {
return false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java
index bb72e12510..c75d81c8b5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileMode.java
@@ -176,12 +176,12 @@ public abstract class FileMode {
* Test a file mode for equality with this
* {@link org.eclipse.jgit.lib.FileMode} object.
*
- * @param modebits
+ * @param modeBits
* a int.
* @return true if the mode bits represent the same mode as this object
*/
@SuppressWarnings("NonOverridingEquals")
- public abstract boolean equals(int modebits);
+ public abstract boolean equals(int modeBits);
/**
* Copy this mode as a sequence of octal US-ASCII bytes.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileModeCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileModeCache.java
new file mode 100644
index 0000000000..073bf7a0ca
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileModeCache.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2023, Thomas Wolf <twolf@apache.org> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.lib;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.InvalidPathException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FileUtils;
+
+/**
+ * A hierarchical cache of {@link FileMode}s per git path.
+ *
+ * @since 6.6.1
+ */
+public class FileModeCache {
+
+ @NonNull
+ private final CacheItem root = new CacheItem(FileMode.TREE);
+
+ @NonNull
+ private final Repository repo;
+
+ /**
+ * Creates a new {@link FileModeCache} for a {@link Repository}.
+ *
+ * @param repo
+ * {@link Repository} this cache is for
+ */
+ public FileModeCache(@NonNull Repository repo) {
+ this.repo = repo;
+ }
+
+ /**
+ * Retrieves the {@link Repository}.
+ *
+ * @return the {@link Repository} this {@link FileModeCache} was created for
+ */
+ @NonNull
+ public Repository getRepository() {
+ return repo;
+ }
+
+ /**
+ * Obtains the {@link CacheItem} for the working tree root.
+ *
+ * @return the {@link CacheItem}
+ */
+ @NonNull
+ public CacheItem getRoot() {
+ return root;
+ }
+
+ /**
+ * Ensure that the given parent directory exists, and cache the information
+ * that gitPath refers to a file.
+ *
+ * @param gitPath
+ * of the file to be written
+ * @param parentDir
+ * directory in which the file shall be placed, assumed to be the
+ * parent of the {@code gitPath}
+ * @param makeSpace
+ * whether to delete a possibly existing file at
+ * {@code parentDir}
+ * @throws IOException
+ * if the directory cannot be created, if necessary
+ */
+ public void safeCreateParentDirectory(String gitPath, File parentDir,
+ boolean makeSpace) throws IOException {
+ CacheItem cachedParent = safeCreateDirectory(gitPath, parentDir,
+ makeSpace);
+ cachedParent.remove(gitPath.substring(gitPath.lastIndexOf('/') + 1));
+ }
+
+ /**
+ * Ensures the given directory {@code dir} with the given git path exists.
+ *
+ * @param gitPath
+ * of a file to be written
+ * @param dir
+ * directory in which the file shall be placed, assumed to be the
+ * parent of the {@code gitPath}
+ * @param makeSpace
+ * whether to remove a file that already at that name
+ * @return A {@link CacheItem} describing the directory, which is guaranteed
+ * to exist
+ * @throws IOException
+ * if the directory cannot be made to exist at the given
+ * location
+ */
+ public CacheItem safeCreateDirectory(String gitPath, File dir,
+ boolean makeSpace) throws IOException {
+ FS fs = repo.getFS();
+ int i = gitPath.lastIndexOf('/');
+ String parentPath = null;
+ if (i >= 0) {
+ if ((makeSpace && dir.isFile()) || fs.isSymLink(dir)) {
+ FileUtils.delete(dir);
+ }
+ parentPath = gitPath.substring(0, i);
+ deleteSymlinkParent(fs, parentPath, repo.getWorkTree());
+ }
+ FileUtils.mkdirs(dir, true);
+ CacheItem cachedParent = getRoot();
+ if (parentPath != null) {
+ cachedParent = add(parentPath, FileMode.TREE);
+ }
+ return cachedParent;
+ }
+
+ private void deleteSymlinkParent(FS fs, String gitPath, File workingTree)
+ throws IOException {
+ if (!fs.supportsSymlinks()) {
+ return;
+ }
+ String[] parts = gitPath.split("/"); //$NON-NLS-1$
+ int n = parts.length;
+ CacheItem cached = getRoot();
+ File p = workingTree;
+ for (int i = 0; i < n; i++) {
+ p = new File(p, parts[i]);
+ CacheItem cachedChild = cached != null ? cached.child(parts[i])
+ : null;
+ boolean delete = false;
+ if (cachedChild != null) {
+ if (FileMode.SYMLINK.equals(cachedChild.getMode())) {
+ delete = true;
+ }
+ } else {
+ try {
+ Path nioPath = FileUtils.toPath(p);
+ BasicFileAttributes attributes = nioPath.getFileSystem()
+ .provider()
+ .getFileAttributeView(nioPath,
+ BasicFileAttributeView.class,
+ LinkOption.NOFOLLOW_LINKS)
+ .readAttributes();
+ if (attributes.isSymbolicLink()) {
+ delete = p.isDirectory();
+ } else if (attributes.isRegularFile()) {
+ break;
+ }
+ } catch (InvalidPathException | IOException e) {
+ // If we can't get the attributes the path does not exist,
+ // or if it does a subsequent mkdirs() will also throw an
+ // exception.
+ break;
+ }
+ }
+ if (delete) {
+ // Deletes the symlink
+ FileUtils.delete(p, FileUtils.SKIP_MISSING);
+ if (cached != null) {
+ cached.remove(parts[i]);
+ }
+ break;
+ }
+ cached = cachedChild;
+ }
+ }
+
+ /**
+ * Records the given {@link FileMode} for the given git path in the cache.
+ * If an entry already exists for the given path, the previously cached file
+ * mode is overwritten.
+ *
+ * @param gitPath
+ * to cache the {@link FileMode} for
+ * @param finalMode
+ * {@link FileMode} to cache
+ * @return the {@link CacheItem} for the path
+ */
+ @NonNull
+ private CacheItem add(String gitPath, FileMode finalMode) {
+ if (gitPath.isEmpty()) {
+ throw new IllegalArgumentException();
+ }
+ String[] parts = gitPath.split("/"); //$NON-NLS-1$
+ int n = parts.length;
+ int i = 0;
+ CacheItem curr = getRoot();
+ while (i < n) {
+ CacheItem next = curr.child(parts[i]);
+ if (next == null) {
+ break;
+ }
+ curr = next;
+ i++;
+ }
+ if (i == n) {
+ curr.setMode(finalMode);
+ } else {
+ while (i < n) {
+ curr = curr.insert(parts[i],
+ i + 1 == n ? finalMode : FileMode.TREE);
+ i++;
+ }
+ }
+ return curr;
+ }
+
+ /**
+ * An item from a {@link FileModeCache}, recording information about a git
+ * path (known from context).
+ */
+ public static class CacheItem {
+
+ @NonNull
+ private FileMode mode;
+
+ private Map<String, CacheItem> children;
+
+ /**
+ * Creates a new {@link CacheItem}.
+ *
+ * @param mode
+ * {@link FileMode} to cache
+ */
+ public CacheItem(@NonNull FileMode mode) {
+ this.mode = mode;
+ }
+
+ /**
+ * Retrieves the cached {@link FileMode}.
+ *
+ * @return the {@link FileMode}
+ */
+ @NonNull
+ public FileMode getMode() {
+ return mode;
+ }
+
+ /**
+ * Retrieves an immediate child of this {@link CacheItem} by name.
+ *
+ * @param childName
+ * name of the child to get
+ * @return the {@link CacheItem}, or {@code null} if no such child is
+ * known
+ */
+ public CacheItem child(String childName) {
+ if (children == null) {
+ return null;
+ }
+ return children.get(childName);
+ }
+
+ /**
+ * Inserts a new cached {@link FileMode} as an immediate child of this
+ * {@link CacheItem}. If there is already a child with the same name, it
+ * is overwritten.
+ *
+ * @param childName
+ * name of the child to create
+ * @param childMode
+ * {@link FileMode} to cache
+ * @return the new {@link CacheItem} created for the child
+ */
+ public CacheItem insert(String childName, @NonNull FileMode childMode) {
+ if (!FileMode.TREE.equals(mode)) {
+ throw new IllegalArgumentException();
+ }
+ if (children == null) {
+ children = new HashMap<>();
+ }
+ CacheItem newItem = new CacheItem(childMode);
+ children.put(childName, newItem);
+ return newItem;
+ }
+
+ /**
+ * Removes the immediate child with the given name.
+ *
+ * @param childName
+ * name of the child to remove
+ * @return the previously cached {@link CacheItem}, if any
+ */
+ public CacheItem remove(String childName) {
+ if (children == null) {
+ return null;
+ }
+ return children.remove(childName);
+ }
+
+ void setMode(@NonNull FileMode mode) {
+ this.mode = mode;
+ if (!FileMode.TREE.equals(mode)) {
+ children = null;
+ }
+ }
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitmoduleEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitmoduleEntry.java
index aa0a9dd663..d03db772ea 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitmoduleEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitmoduleEntry.java
@@ -36,6 +36,8 @@ public final class GitmoduleEntry {
}
/**
+ * Get Id of a .gitmodules file found in the pack
+ *
* @return Id of a .gitmodules file found in the pack
*/
public AnyObjectId getBlobId() {
@@ -43,6 +45,8 @@ public final class GitmoduleEntry {
}
/**
+ * Get Id of a tree object where the .gitmodules file was found
+ *
* @return Id of a tree object where the .gitmodules file was found
*/
public AnyObjectId getTreeId() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java
index 427a235f3b..23d16db39f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgConfig.java
@@ -24,7 +24,13 @@ public class GpgConfig {
/** Value for openpgp */
OPENPGP("openpgp"), //$NON-NLS-1$
/** Value for x509 */
- X509("x509"); //$NON-NLS-1$
+ X509("x509"), //$NON-NLS-1$
+ /**
+ * Value for ssh.
+ *
+ * @since 7.0
+ */
+ SSH("ssh"); //$NON-NLS-1$
private final String configValue;
@@ -55,26 +61,11 @@ public class GpgConfig {
private final boolean forceAnnotated;
- /**
- * Create a {@link GpgConfig} with the given parameters and default
- * {@code true} for signing commits and {@code false} for tags.
- *
- * @param keySpec
- * to use
- * @param format
- * to use
- * @param gpgProgram
- * to use
- * @since 5.11
- */
- public GpgConfig(String keySpec, GpgFormat format, String gpgProgram) {
- keyFormat = format;
- signingKey = keySpec;
- program = gpgProgram;
- signCommits = true;
- signAllTags = false;
- forceAnnotated = false;
- }
+ private final String sshDefaultKeyCommand;
+
+ private final String sshAllowedSignersFile;
+
+ private final String sshRevocationFile;
/**
* Create a new GPG config that reads the configuration from config.
@@ -83,18 +74,18 @@ public class GpgConfig {
* the config to read from
*/
public GpgConfig(Config config) {
- keyFormat = config.getEnum(GpgFormat.values(),
- ConfigConstants.CONFIG_GPG_SECTION, null,
+ keyFormat = config.getEnum(ConfigConstants.CONFIG_GPG_SECTION, null,
ConfigConstants.CONFIG_KEY_FORMAT, GpgFormat.OPENPGP);
signingKey = config.getString(ConfigConstants.CONFIG_USER_SECTION, null,
ConfigConstants.CONFIG_KEY_SIGNINGKEY);
String exe = config.getString(ConfigConstants.CONFIG_GPG_SECTION,
keyFormat.toConfigValue(), ConfigConstants.CONFIG_KEY_PROGRAM);
- if (exe == null) {
+ if (exe == null && GpgFormat.OPENPGP.equals(keyFormat)) {
exe = config.getString(ConfigConstants.CONFIG_GPG_SECTION, null,
ConfigConstants.CONFIG_KEY_PROGRAM);
}
+
program = exe;
signCommits = config.getBoolean(ConfigConstants.CONFIG_COMMIT_SECTION,
ConfigConstants.CONFIG_KEY_GPGSIGN, false);
@@ -102,6 +93,17 @@ public class GpgConfig {
ConfigConstants.CONFIG_KEY_GPGSIGN, false);
forceAnnotated = config.getBoolean(ConfigConstants.CONFIG_TAG_SECTION,
ConfigConstants.CONFIG_KEY_FORCE_SIGN_ANNOTATED, false);
+ sshDefaultKeyCommand = config.getString(
+ ConfigConstants.CONFIG_GPG_SECTION,
+ ConfigConstants.CONFIG_SSH_SUBSECTION,
+ ConfigConstants.CONFIG_KEY_SSH_DEFAULT_KEY_COMMAND);
+ sshAllowedSignersFile = config.getString(
+ ConfigConstants.CONFIG_GPG_SECTION,
+ ConfigConstants.CONFIG_SSH_SUBSECTION,
+ ConfigConstants.CONFIG_KEY_SSH_ALLOWED_SIGNERS_FILE);
+ sshRevocationFile = config.getString(ConfigConstants.CONFIG_GPG_SECTION,
+ ConfigConstants.CONFIG_SSH_SUBSECTION,
+ ConfigConstants.CONFIG_KEY_SSH_REVOCATION_FILE);
}
/**
@@ -165,4 +167,37 @@ public class GpgConfig {
public boolean isSignAnnotated() {
return forceAnnotated;
}
+
+ /**
+ * Retrieves the value of git config {@code gpg.ssh.defaultKeyCommand}.
+ *
+ * @return the value of {@code gpg.ssh.defaultKeyCommand}
+ *
+ * @since 7.1
+ */
+ public String getSshDefaultKeyCommand() {
+ return sshDefaultKeyCommand;
+ }
+
+ /**
+ * Retrieves the value of git config {@code gpg.ssh.allowedSignersFile}.
+ *
+ * @return the value of {@code gpg.ssh.allowedSignersFile}
+ *
+ * @since 7.1
+ */
+ public String getSshAllowedSignersFile() {
+ return sshAllowedSignersFile;
+ }
+
+ /**
+ * Retrieves the value of git config {@code gpg.ssh.revocationFile}.
+ *
+ * @return the value of {@code gpg.ssh.revocationFile}
+ *
+ * @since 7.1
+ */
+ public String getSshRevocationFile() {
+ return sshRevocationFile;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java
deleted file mode 100644
index 074f46567b..0000000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2020 Thomas Wolf <thomas.wolf@paranor.ch> and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
-package org.eclipse.jgit.lib;
-
-import org.eclipse.jgit.annotations.NonNull;
-import org.eclipse.jgit.annotations.Nullable;
-import org.eclipse.jgit.api.errors.CanceledException;
-import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException;
-import org.eclipse.jgit.transport.CredentialsProvider;
-
-/**
- * Creates GPG signatures for Git objects.
- *
- * @since 5.11
- */
-public interface GpgObjectSigner {
-
- /**
- * Signs the specified object.
- *
- * <p>
- * Implementors should obtain the payload for signing from the specified
- * object via {@link ObjectBuilder#build()} and create a proper
- * {@link GpgSignature}. The generated signature must be set on the
- * specified {@code object} (see
- * {@link ObjectBuilder#setGpgSignature(GpgSignature)}).
- * </p>
- * <p>
- * Any existing signature on the object must be discarded prior obtaining
- * the payload via {@link ObjectBuilder#build()}.
- * </p>
- *
- * @param object
- * the object to sign (must not be {@code null} and must be
- * complete to allow proper calculation of payload)
- * @param gpgSigningKey
- * the signing key to locate (passed as is to the GPG signing
- * tool as is; eg., value of <code>user.signingkey</code>)
- * @param committer
- * the signing identity (to help with key lookup in case signing
- * key is not specified)
- * @param credentialsProvider
- * provider to use when querying for signing key credentials (eg.
- * passphrase)
- * @param config
- * GPG settings from the git config
- * @throws CanceledException
- * when signing was canceled (eg., user aborted when entering
- * passphrase)
- * @throws UnsupportedSigningFormatException
- * if a config is given and the wanted key format is not
- * supported
- */
- void signObject(@NonNull ObjectBuilder object,
- @Nullable String gpgSigningKey, @NonNull PersonIdent committer,
- CredentialsProvider credentialsProvider, GpgConfig config)
- throws CanceledException, UnsupportedSigningFormatException;
-
- /**
- * Indicates if a signing key is available for the specified committer
- * and/or signing key.
- *
- * @param gpgSigningKey
- * the signing key to locate (passed as is to the GPG signing
- * tool as is; eg., value of <code>user.signingkey</code>)
- * @param committer
- * the signing identity (to help with key lookup in case signing
- * key is not specified)
- * @param credentialsProvider
- * provider to use when querying for signing key credentials (eg.
- * passphrase)
- * @param config
- * GPG settings from the git config
- * @return <code>true</code> if a signing key is available,
- * <code>false</code> otherwise
- * @throws CanceledException
- * when signing was canceled (eg., user aborted when entering
- * passphrase)
- * @throws UnsupportedSigningFormatException
- * if a config is given and the wanted key format is not
- * supported
- */
- public abstract boolean canLocateSigningKey(@Nullable String gpgSigningKey,
- @NonNull PersonIdent committer,
- CredentialsProvider credentialsProvider, GpgConfig config)
- throws CanceledException, UnsupportedSigningFormatException;
-
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignature.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignature.java
index e78cf16ea4..4018dabebe 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignature.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignature.java
@@ -49,7 +49,6 @@ public class GpgSignature implements Serializable {
return new String(signature, US_ASCII);
}
- /** {@inheritDoc} */
@Override
@SuppressWarnings("nls")
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.java
deleted file mode 100644
index a7a39c998f..0000000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifier.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-import java.util.Date;
-
-import org.eclipse.jgit.annotations.NonNull;
-import org.eclipse.jgit.annotations.Nullable;
-import org.eclipse.jgit.api.errors.JGitInternalException;
-import org.eclipse.jgit.revwalk.RevObject;
-
-/**
- * A {@code GpgVerifier} can verify GPG signatures on git commits and tags.
- *
- * @since 5.11
- */
-public interface GpgSignatureVerifier {
-
- /**
- * Verifies the signature on a signed commit or tag.
- *
- * @param object
- * to verify
- * @param config
- * the {@link GpgConfig} to use
- * @return a {@link SignatureVerification} describing the outcome of the
- * verification, or {@code null} if the object was not signed
- * @throws IOException
- * if an error occurs getting a public key
- * @throws org.eclipse.jgit.api.errors.JGitInternalException
- * if signature verification fails
- */
- @Nullable
- SignatureVerification verifySignature(@NonNull RevObject object,
- @NonNull GpgConfig config) throws IOException;
-
-
- /**
- * Verifies a given signature for given data.
- *
- * @param data
- * the signature is for
- * @param signatureData
- * the ASCII-armored signature
- * @return a {@link SignatureVerification} describing the outcome
- * @throws IOException
- * if the signature cannot be parsed
- * @throws JGitInternalException
- * if signature verification fails
- */
- public SignatureVerification verify(byte[] data, byte[] signatureData)
- throws IOException;
-
- /**
- * Retrieves the name of this verifier. This should be a short string
- * identifying the engine that verified the signature, like "gpg" if GPG is
- * used, or "bc" for a BouncyCastle implementation.
- *
- * @return the name
- */
- @NonNull
- String getName();
-
- /**
- * A {@link GpgSignatureVerifier} may cache public keys to speed up
- * verifying signatures on multiple objects. This clears this cache, if any.
- */
- void clear();
-
- /**
- * A {@code SignatureVerification} returns data about a (positively or
- * negatively) verified signature.
- */
- interface SignatureVerification {
-
- // Data about the signature.
-
- @NonNull
- Date getCreationDate();
-
- // Data from the signature used to find a public key.
-
- /**
- * Obtains the signer as stored in the signature, if known.
- *
- * @return the signer, or {@code null} if unknown
- */
- String getSigner();
-
- /**
- * Obtains the short or long fingerprint of the public key as stored in
- * the signature, if known.
- *
- * @return the fingerprint, or {@code null} if unknown
- */
- String getKeyFingerprint();
-
- // Some information about the found public key.
-
- /**
- * Obtains the OpenPGP user ID associated with the key.
- *
- * @return the user id, or {@code null} if unknown
- */
- String getKeyUser();
-
- /**
- * Tells whether the public key used for this signature verification was
- * expired when the signature was created.
- *
- * @return {@code true} if the key was expired already, {@code false}
- * otherwise
- */
- boolean isExpired();
-
- /**
- * Obtains the trust level of the public key used to verify the
- * signature.
- *
- * @return the trust level
- */
- @NonNull
- TrustLevel getTrustLevel();
-
- // The verification result.
-
- /**
- * Tells whether the signature verification was successful.
- *
- * @return {@code true} if the signature was verified successfully;
- * {@code false} if not.
- */
- boolean getVerified();
-
- /**
- * Obtains a human-readable message giving additional information about
- * the outcome of the verification.
- *
- * @return the message, or {@code null} if none set.
- */
- String getMessage();
- }
-
- /**
- * The owner's trust in a public key.
- */
- enum TrustLevel {
- UNKNOWN, NEVER, MARGINAL, FULL, ULTIMATE
- }
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifierFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifierFactory.java
deleted file mode 100644
index 59775c475b..0000000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSignatureVerifierFactory.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2021, 2022 Thomas Wolf <thomas.wolf@paranor.ch> and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
-package org.eclipse.jgit.lib;
-
-import java.util.Iterator;
-import java.util.ServiceConfigurationError;
-import java.util.ServiceLoader;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A {@code GpgSignatureVerifierFactory} creates {@link GpgSignatureVerifier} instances.
- *
- * @since 5.11
- */
-public abstract class GpgSignatureVerifierFactory {
-
- private static final Logger LOG = LoggerFactory
- .getLogger(GpgSignatureVerifierFactory.class);
-
- private static class DefaultFactory {
-
- private static volatile GpgSignatureVerifierFactory defaultFactory = loadDefault();
-
- private static GpgSignatureVerifierFactory loadDefault() {
- try {
- ServiceLoader<GpgSignatureVerifierFactory> loader = ServiceLoader
- .load(GpgSignatureVerifierFactory.class);
- Iterator<GpgSignatureVerifierFactory> iter = loader.iterator();
- if (iter.hasNext()) {
- return iter.next();
- }
- } catch (ServiceConfigurationError e) {
- LOG.error(e.getMessage(), e);
- }
- return null;
- }
-
- private DefaultFactory() {
- // No instantiation
- }
-
- public static GpgSignatureVerifierFactory getDefault() {
- return defaultFactory;
- }
-
- /**
- * Sets the default factory.
- *
- * @param factory
- * the new default factory
- */
- public static void setDefault(GpgSignatureVerifierFactory factory) {
- defaultFactory = factory;
- }
- }
-
- /**
- * Retrieves the default factory.
- *
- * @return the default factory or {@code null} if none set
- */
- public static GpgSignatureVerifierFactory getDefault() {
- return DefaultFactory.getDefault();
- }
-
- /**
- * Sets the default factory.
- *
- * @param factory
- * the new default factory
- */
- public static void setDefault(GpgSignatureVerifierFactory factory) {
- DefaultFactory.setDefault(factory);
- }
-
- /**
- * Creates a new {@link GpgSignatureVerifier}.
- *
- * @return the new {@link GpgSignatureVerifier}
- */
- public abstract GpgSignatureVerifier getVerifier();
-
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java
deleted file mode 100644
index b25a61b506..0000000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgSigner.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2018, 2022 Salesforce and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
-package org.eclipse.jgit.lib;
-
-import java.util.Iterator;
-import java.util.ServiceConfigurationError;
-import java.util.ServiceLoader;
-
-import org.eclipse.jgit.annotations.NonNull;
-import org.eclipse.jgit.annotations.Nullable;
-import org.eclipse.jgit.api.errors.CanceledException;
-import org.eclipse.jgit.transport.CredentialsProvider;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Creates GPG signatures for Git objects.
- *
- * @since 5.3
- */
-public abstract class GpgSigner {
-
- private static final Logger LOG = LoggerFactory.getLogger(GpgSigner.class);
-
- private static class DefaultSigner {
-
- private static volatile GpgSigner defaultSigner = loadGpgSigner();
-
- private static GpgSigner loadGpgSigner() {
- try {
- ServiceLoader<GpgSigner> loader = ServiceLoader
- .load(GpgSigner.class);
- Iterator<GpgSigner> iter = loader.iterator();
- if (iter.hasNext()) {
- return iter.next();
- }
- } catch (ServiceConfigurationError e) {
- LOG.error(e.getMessage(), e);
- }
- return null;
- }
-
- private DefaultSigner() {
- // No instantiation
- }
-
- public static GpgSigner getDefault() {
- return defaultSigner;
- }
-
- public static void setDefault(GpgSigner signer) {
- defaultSigner = signer;
- }
- }
-
- /**
- * Get the default signer, or <code>null</code>.
- *
- * @return the default signer, or <code>null</code>.
- */
- public static GpgSigner getDefault() {
- return DefaultSigner.getDefault();
- }
-
- /**
- * Set the default signer.
- *
- * @param signer
- * the new default signer, may be <code>null</code> to select no
- * default.
- */
- public static void setDefault(GpgSigner signer) {
- DefaultSigner.setDefault(signer);
- }
-
- /**
- * Signs the specified commit.
- *
- * <p>
- * Implementors should obtain the payload for signing from the specified
- * commit via {@link CommitBuilder#build()} and create a proper
- * {@link GpgSignature}. The generated signature must be set on the
- * specified {@code commit} (see
- * {@link CommitBuilder#setGpgSignature(GpgSignature)}).
- * </p>
- * <p>
- * Any existing signature on the commit must be discarded prior obtaining
- * the payload via {@link CommitBuilder#build()}.
- * </p>
- *
- * @param commit
- * the commit to sign (must not be <code>null</code> and must be
- * complete to allow proper calculation of payload)
- * @param gpgSigningKey
- * the signing key to locate (passed as is to the GPG signing
- * tool as is; eg., value of <code>user.signingkey</code>)
- * @param committer
- * the signing identity (to help with key lookup in case signing
- * key is not specified)
- * @param credentialsProvider
- * provider to use when querying for signing key credentials (eg.
- * passphrase)
- * @throws CanceledException
- * when signing was canceled (eg., user aborted when entering
- * passphrase)
- */
- public abstract void sign(@NonNull CommitBuilder commit,
- @Nullable String gpgSigningKey, @NonNull PersonIdent committer,
- CredentialsProvider credentialsProvider) throws CanceledException;
-
- /**
- * Indicates if a signing key is available for the specified committer
- * and/or signing key.
- *
- * @param gpgSigningKey
- * the signing key to locate (passed as is to the GPG signing
- * tool as is; eg., value of <code>user.signingkey</code>)
- * @param committer
- * the signing identity (to help with key lookup in case signing
- * key is not specified)
- * @param credentialsProvider
- * provider to use when querying for signing key credentials (eg.
- * passphrase)
- * @return <code>true</code> if a signing key is available,
- * <code>false</code> otherwise
- * @throws CanceledException
- * when signing was canceled (eg., user aborted when entering
- * passphrase)
- */
- public abstract boolean canLocateSigningKey(@Nullable String gpgSigningKey,
- @NonNull PersonIdent committer,
- CredentialsProvider credentialsProvider) throws CanceledException;
-
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
index df9fd47efa..a99c64701f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
@@ -122,6 +122,8 @@ public class IndexDiff {
}
/**
+ * Whether there is a "base" stage entry
+ *
* @return whether there is a "base" stage entry
*/
public boolean hasBase() {
@@ -129,6 +131,8 @@ public class IndexDiff {
}
/**
+ * Whether there is an "ours" stage entry
+ *
* @return whether there is an "ours" stage entry
*/
public boolean hasOurs() {
@@ -136,6 +140,8 @@ public class IndexDiff {
}
/**
+ * Whether there is a "theirs" stage entry
+ *
* @return whether there is a "theirs" stage entry
*/
public boolean hasTheirs() {
@@ -265,6 +271,7 @@ public class IndexDiff {
* @param workingTreeIterator
* iterator for working directory
* @throws java.io.IOException
+ * if an IO error occurred
*/
public IndexDiff(Repository repository, String revstr,
WorkingTreeIterator workingTreeIterator) throws IOException {
@@ -281,6 +288,7 @@ public class IndexDiff {
* @param workingTreeIterator
* iterator for working directory
* @throws java.io.IOException
+ * if an IO error occurred
*/
public IndexDiff(Repository repository, ObjectId objectId,
WorkingTreeIterator workingTreeIterator) throws IOException {
@@ -312,6 +320,8 @@ public class IndexDiff {
*/
public interface WorkingTreeIteratorFactory {
/**
+ * Get working tree iterator
+ *
* @param repo
* the repository
* @return working tree iterator
@@ -325,6 +335,7 @@ public class IndexDiff {
* Allows higher layers to set the factory for WorkingTreeIterators.
*
* @param wTreeIt
+ * working tree iterator factory
* @since 3.6
*/
public void setWorkingTreeItFactory(WorkingTreeIteratorFactory wTreeIt) {
@@ -349,6 +360,7 @@ public class IndexDiff {
*
* @return if anything is different between index, tree, and workdir
* @throws java.io.IOException
+ * if an IO error occurred
*/
public boolean diff() throws IOException {
return diff(null);
@@ -372,6 +384,7 @@ public class IndexDiff {
* {@link RepositoryBuilder}.
* @return if anything is different between index, tree, and workdir
* @throws java.io.IOException
+ * if an IO error occurred
* @since 5.6
*/
public boolean diff(RepositoryBuilderFactory factory)
@@ -395,9 +408,11 @@ public class IndexDiff {
* number or estimated files in the working tree
* @param estIndexSize
* number of estimated entries in the cache
- * @param title a {@link java.lang.String} object.
+ * @param title
+ * a {@link java.lang.String} object.
* @return if anything is different between index, tree, and workdir
* @throws java.io.IOException
+ * if an IO error occurred
*/
public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
int estIndexSize, final String title)
@@ -436,6 +451,7 @@ public class IndexDiff {
* {@link RepositoryBuilder}.
* @return if anything is different between index, tree, and workdir
* @throws java.io.IOException
+ * if an IO error occurred
* @since 5.6
*/
public boolean diff(ProgressMonitor monitor, int estWorkTreeSize,
@@ -623,7 +639,7 @@ public class IndexDiff {
// submodule repository in .git/modules doesn't
// exist yet it isn't "missing".
File gitDir = new File(
- new File(repository.getDirectory(),
+ new File(repository.getCommonDirectory(),
Constants.MODULES),
subRepoPath);
if (!gitDir.isDirectory()) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java
index 4a712ba360..6a35dedcf5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/MutableObjectId.java
@@ -242,7 +242,6 @@ public class MutableObjectId extends AnyObjectId {
}
}
- /** {@inheritDoc} */
@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
index 127cca9d1b..0a4db2a5f6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/NullProgressMonitor.java
@@ -23,31 +23,26 @@ public class NullProgressMonitor implements ProgressMonitor {
// Do not let others instantiate
}
- /** {@inheritDoc} */
@Override
public void start(int totalTasks) {
// Do not report.
}
- /** {@inheritDoc} */
@Override
public void beginTask(String title, int totalWork) {
// Do not report.
}
- /** {@inheritDoc} */
@Override
public void update(int completed) {
// Do not report.
}
- /** {@inheritDoc} */
@Override
public boolean isCancelled() {
return false;
}
- /** {@inheritDoc} */
@Override
public void endTask() {
// Do not report.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectBuilder.java
index 4b7054f72b..064d89e084 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectBuilder.java
@@ -72,7 +72,6 @@ public abstract class ObjectBuilder {
* <li>create {@link GpgSignature} from payload</li>
* <li>set {@link GpgSignature}</li>
* </ol>
- * </p>
*
* @param gpgSignature
* the signature to set or {@code null} to unset
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
index 9e9ef882a3..1a6c8ad0d2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
@@ -116,46 +116,103 @@ public class ObjectChecker {
* @since 4.2
*/
public enum ErrorType {
- // @formatter:off
// These names match git-core so that fsck section keys also match.
- /***/ NULL_SHA1,
- /***/ DUPLICATE_ENTRIES,
- /***/ TREE_NOT_SORTED,
- /***/ ZERO_PADDED_FILEMODE,
- /***/ EMPTY_NAME,
- /***/ FULL_PATHNAME,
- /***/ HAS_DOT,
- /***/ HAS_DOTDOT,
- /***/ HAS_DOTGIT,
- /***/ BAD_OBJECT_SHA1,
- /***/ BAD_PARENT_SHA1,
- /***/ BAD_TREE_SHA1,
- /***/ MISSING_AUTHOR,
- /***/ MISSING_COMMITTER,
- /***/ MISSING_OBJECT,
- /***/ MISSING_TREE,
- /***/ MISSING_TYPE_ENTRY,
- /***/ MISSING_TAG_ENTRY,
- /***/ BAD_DATE,
- /***/ BAD_EMAIL,
- /***/ BAD_TIMEZONE,
- /***/ MISSING_EMAIL,
- /***/ MISSING_SPACE_BEFORE_DATE,
- /** @since 5.2 */ GITMODULES_BLOB,
- /** @since 5.2 */ GITMODULES_LARGE,
- /** @since 5.2 */ GITMODULES_NAME,
- /** @since 5.2 */ GITMODULES_PARSE,
- /** @since 5.2 */ GITMODULES_PATH,
- /** @since 5.2 */ GITMODULES_SYMLINK,
- /** @since 5.2 */ GITMODULES_URL,
- /***/ UNKNOWN_TYPE,
+ // See https://git-scm.com/docs/git-fsck#_fsck_messages
+ /** (WARN) Tree contains entries pointing to a null sha1. */
+ NULL_SHA1,
+ /** (ERROR) A tree contains duplicate file entries. */
+ DUPLICATE_ENTRIES,
+ /** (ERROR) A tree is not properly sorted. */
+ TREE_NOT_SORTED,
+ /** (WARN) Found a zero padded filemode in a tree. */
+ ZERO_PADDED_FILEMODE,
+ /** (WARN) A path contains an empty name. */
+ EMPTY_NAME,
+ /** (WARN) A path contains the full path starting with "/". */
+ FULL_PATHNAME,
+ /** (WARN) A tree contains an entry named . */
+ HAS_DOT,
+ /** (WARN) A tree contains an entry named .. */
+ HAS_DOTDOT,
+ /** (WARN) A tree contains an entry named .git */
+ HAS_DOTGIT,
+ /** (ERROR) An object has a bad sha1. */
+ BAD_OBJECT_SHA1,
+ /** (ERROR) A commit object has a bad parent sha1. */
+ BAD_PARENT_SHA1,
+ /** (ERROR) A tree has an invalid format. */
+ BAD_TREE_SHA1,
+ /** (ERROR) Author is missing. */
+ MISSING_AUTHOR,
+ /** (ERROR) Committer is missing. */
+ MISSING_COMMITTER,
+ /** (ERROR) Missing object line in tag object. */
+ MISSING_OBJECT,
+ /** (ERROR) Missing tree line in a commit object. */
+ MISSING_TREE,
+ /** (ERROR) Missing type line in a tag object. */
+ MISSING_TYPE_ENTRY,
+ /** (ERROR) Missing tag line in a tag object. */
+ MISSING_TAG_ENTRY,
+ /** (ERROR) Invalid date format in an author/committer line. */
+ BAD_DATE,
+ /** (ERROR) Invalid email format in an author/committer line. */
+ BAD_EMAIL,
+ /** (ERROR) Found an invalid time zone in an author/committer line. */
+ BAD_TIMEZONE,
+ /** (ERROR) Email is missing in an author/committer line. */
+ MISSING_EMAIL,
+ /** (ERROR) Missing space before date in an author/committer line. */
+ MISSING_SPACE_BEFORE_DATE,
+ /**
+ * (ERROR) A non-blob found at .gitmodules.
+ * @since 5.2
+ */
+ GITMODULES_BLOB,
+ /**
+ * (ERROR) The .gitmodules file is too large to parse.
+ * @since 5.2
+ */
+ GITMODULES_LARGE,
+ /**
+ * (ERROR) A submodule name is invalid.
+ * @since 5.2
+ */
+ GITMODULES_NAME,
+ /**
+ * (INFO) Could not parse .gitmodules blob.
+ * @since 5.2
+ */
+ GITMODULES_PARSE,
+ /**
+ * (ERROR) .gitmodules path is invalid.
+ * @since 5.2
+ */
+ GITMODULES_PATH,
+ /**
+ * (ERROR) .gitmodules is a symlink.
+ * @since 5.2
+ */
+ GITMODULES_SYMLINK,
+ /**
+ * (ERROR) Found an invalid submodule url.
+ * @since 5.2
+ */
+ GITMODULES_URL,
+ /** (ERROR) Found an unknown object type. */
+ UNKNOWN_TYPE,
// These are unique to JGit.
- /***/ WIN32_BAD_NAME,
- /***/ BAD_UTF8;
- // @formatter:on
-
- /** @return camelCaseVersion of the name. */
+ /** (ERROR) Windows: Invalid name */
+ WIN32_BAD_NAME,
+ /** (ERROR) Byte sequence is not a valid UTF-8 character */
+ BAD_UTF8;
+
+ /**
+ * Get camelCaseVersion of the name
+ *
+ * @return camelCaseVersion of the name.
+ */
public String getMessageId() {
String n = name();
StringBuilder r = new StringBuilder(n.length());
@@ -1065,6 +1122,7 @@ public class ObjectChecker {
*
* @return true if the filename in buf could be a ".gitmodules" file
* @throws CorruptObjectException
+ * if an object is corrupt
*/
private boolean isGitmodules(byte[] buf, int start, int end, @Nullable AnyObjectId id)
throws CorruptObjectException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
index a39766cbd0..a8cc9a8529 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
@@ -77,9 +77,12 @@ public abstract class ObjectDatabase implements AutoCloseable {
public abstract ObjectReader newReader();
/**
+ * Get the shallow commits of the current repository
+ *
* @return the shallow commits of the current repository
*
- * @throws IOException the database could not be read
+ * @throws IOException
+ * the database could not be read
*
* @since 6.3
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java
index 269049f4a2..1b455b974d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectId.java
@@ -15,6 +15,7 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
+import java.nio.ByteBuffer;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.InvalidObjectIdException;
@@ -152,6 +153,22 @@ public class ObjectId extends AnyObjectId implements Serializable {
}
/**
+ * Convert an ObjectId from raw binary representation
+ *
+ * @param bb
+ * a bytebuffer with the objectid encoded as 5 consecutive ints.
+ * This is the reverse of {@link ObjectId#copyRawTo(ByteBuffer)}
+ *
+ * @return the converted object id.
+ *
+ * @since 7.0
+ */
+ public static final ObjectId fromRaw(ByteBuffer bb) {
+ return new ObjectId(bb.getInt(), bb.getInt(), bb.getInt(), bb.getInt(),
+ bb.getInt());
+ }
+
+ /**
* Convert an ObjectId from raw binary representation.
*
* @param is
@@ -262,7 +279,6 @@ public class ObjectId extends AnyObjectId implements Serializable {
w5 = src.w5;
}
- /** {@inheritDoc} */
@Override
public ObjectId toObjectId() {
return this;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java
index 29039097f1..a74fddff42 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java
@@ -128,8 +128,11 @@ public class ObjectIdOwnerMap<V extends ObjectIdOwnerMap.Entry>
* <p>
* An existing mapping for <b>must not</b> be in this map. Callers must
* first call {@link #get(AnyObjectId)} to verify there is no current
- * mapping prior to adding a new mapping, or use {@link #addIfAbsent(Entry)}.
+ * mapping prior to adding a new mapping, or use
+ * {@link #addIfAbsent(Entry)}.
*
+ * @param <Q>
+ * type of values
* @param newValue
* the object to store.
*/
@@ -157,6 +160,8 @@ public class ObjectIdOwnerMap<V extends ObjectIdOwnerMap.Entry>
* boolean wasNew = map.addIfAbsent(obj) == obj;
* </pre>
*
+ * @param <Q>
+ * type of values
* @param newValue
* the object to store.
* @return {@code newValue} if stored, or the prior value already stored and
@@ -199,7 +204,6 @@ public class ObjectIdOwnerMap<V extends ObjectIdOwnerMap.Entry>
return size == 0;
}
- /** {@inheritDoc} */
@Override
public Iterator<V> iterator() {
return new Iterator<>() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
index a04ca6890c..c3c58372b1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
@@ -204,41 +204,35 @@ public abstract class ObjectIdRef implements Ref {
this.updateIndex = updateIndex;
}
- /** {@inheritDoc} */
@Override
@NonNull
public String getName() {
return name;
}
- /** {@inheritDoc} */
@Override
public boolean isSymbolic() {
return false;
}
- /** {@inheritDoc} */
@Override
@NonNull
public Ref getLeaf() {
return this;
}
- /** {@inheritDoc} */
@Override
@NonNull
public Ref getTarget() {
return this;
}
- /** {@inheritDoc} */
@Override
@Nullable
public ObjectId getObjectId() {
return objectId;
}
- /** {@inheritDoc} */
@Override
@NonNull
public Storage getStorage() {
@@ -257,7 +251,6 @@ public abstract class ObjectIdRef implements Ref {
return updateIndex;
}
- /** {@inheritDoc} */
@NonNull
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
index 0e015b658b..d4acfd4714 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
@@ -96,6 +96,8 @@ public class ObjectIdSubclassMap<V extends ObjectId>
* mapping prior to adding a new mapping, or use
* {@link #addIfAbsent(ObjectId)}.
*
+ * @param <Q>
+ * type of values
* @param newValue
* the object to store.
*/
@@ -117,6 +119,8 @@ public class ObjectIdSubclassMap<V extends ObjectId>
* boolean wasNew = map.addIfAbsent(obj) == obj;
* </pre>
*
+ * @param <Q>
+ * type of values
* @param newValue
* the object to store.
* @return {@code newValue} if stored, or the prior value already stored and
@@ -162,7 +166,6 @@ public class ObjectIdSubclassMap<V extends ObjectId>
return size == 0;
}
- /** {@inheritDoc} */
@Override
public Iterator<V> iterator() {
return new Iterator<>() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
index 000899d767..f6c52e292a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
@@ -66,7 +66,11 @@ public abstract class ObjectInserter implements AutoCloseable {
/** Wraps a delegate ObjectInserter. */
public abstract static class Filter extends ObjectInserter {
- /** @return delegate ObjectInserter to handle all processing. */
+ /**
+ * Get delegate ObjectInserter to handle all processing
+ *
+ * @return delegate ObjectInserter to handle all processing.
+ */
protected abstract ObjectInserter delegate();
@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java
index 67b8a2c249..d0564265dc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java
@@ -302,6 +302,8 @@ public abstract class ObjectLoader {
*/
public abstract static class Filter extends ObjectLoader {
/**
+ * Get delegate ObjectLoader to handle all processing.
+ *
* @return delegate ObjectLoader to handle all processing.
* @since 4.10
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java
index 69b2b5104e..b916d1e8e9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java
@@ -245,12 +245,15 @@ public abstract class ObjectReader implements AutoCloseable {
*
* @return IDs of shallow commits
* @throws java.io.IOException
+ * if an error occurred
*/
public abstract Set<ObjectId> getShallowCommits() throws IOException;
/**
* Asynchronous object opening.
*
+ * @param <T>
+ * type of {@code ObjectId}
* @param objectIds
* objects to open from the object store. The supplied collection
* must not be modified until the queue has finished.
@@ -370,6 +373,8 @@ public abstract class ObjectReader implements AutoCloseable {
/**
* Asynchronous object size lookup.
*
+ * @param <T>
+ * type of {@code ObjectId}
* @param objectIds
* objects to get the size of from the object store. The supplied
* collection must not be modified until the queue has finished.
@@ -578,6 +583,8 @@ public abstract class ObjectReader implements AutoCloseable {
*/
public abstract static class Filter extends ObjectReader {
/**
+ * Get delegate ObjectReader to handle all processing
+ *
* @return delegate ObjectReader to handle all processing.
* @since 4.4
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
index 93710299b4..50f4a83b93 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/PersonIdent.java
@@ -12,10 +12,15 @@
package org.eclipse.jgit.lib;
+import static java.time.ZoneOffset.UTC;
+
import java.io.Serializable;
-import java.text.SimpleDateFormat;
+import java.time.DateTimeException;
import java.time.Instant;
import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
@@ -40,7 +45,9 @@ public class PersonIdent implements Serializable {
* timezone offset as in {@link #getTimeZoneOffset()}.
* @return time zone object for the given offset.
* @since 4.1
+ * @deprecated use {@link #getZoneId(int)} instead
*/
+ @Deprecated(since = "7.2")
public static TimeZone getTimeZone(int tzOffset) {
StringBuilder tzId = new StringBuilder(8);
tzId.append("GMT"); //$NON-NLS-1$
@@ -49,6 +56,21 @@ public class PersonIdent implements Serializable {
}
/**
+ * Translate a minutes offset into a ZoneId
+ *
+ * @param tzOffset as minutes east of UTC
+ * @return a ZoneId for this offset (UTC if invalid)
+ * @since 7.1
+ */
+ public static ZoneId getZoneId(int tzOffset) {
+ try {
+ return ZoneOffset.ofHoursMinutes(tzOffset / 60, tzOffset % 60);
+ } catch (DateTimeException e) {
+ return UTC;
+ }
+ }
+
+ /**
* Format a timezone offset.
*
* @param r
@@ -121,13 +143,17 @@ public class PersonIdent implements Serializable {
}
}
+ // Write offsets as [+-]HHMM
+ private static final DateTimeFormatter OFFSET_FORMATTER = DateTimeFormatter
+ .ofPattern("Z", Locale.US); //$NON-NLS-1$
+
private final String name;
private final String emailAddress;
- private final long when;
+ private final Instant when;
- private final int tzOffset;
+ private final ZoneId tzOffset;
/**
* Creates new PersonIdent from config info in repository, with current time.
@@ -160,7 +186,7 @@ public class PersonIdent implements Serializable {
* a {@link java.lang.String} object.
*/
public PersonIdent(String aName, String aEmailAddress) {
- this(aName, aEmailAddress, SystemReader.getInstance().getCurrentTime());
+ this(aName, aEmailAddress, SystemReader.getInstance().now());
}
/**
@@ -177,7 +203,7 @@ public class PersonIdent implements Serializable {
*/
public PersonIdent(String aName, String aEmailAddress,
ProposedTimestamp when) {
- this(aName, aEmailAddress, when.millis());
+ this(aName, aEmailAddress, when.instant());
}
/**
@@ -189,8 +215,25 @@ public class PersonIdent implements Serializable {
* local time
* @param tz
* time zone
+ * @deprecated Use {@link #PersonIdent(PersonIdent, Instant, ZoneId)} instead.
*/
+ @Deprecated(since = "7.1")
public PersonIdent(PersonIdent pi, Date when, TimeZone tz) {
+ this(pi.getName(), pi.getEmailAddress(), when.toInstant(), tz.toZoneId());
+ }
+
+ /**
+ * Copy a PersonIdent, but alter the clone's time stamp
+ *
+ * @param pi
+ * original {@link org.eclipse.jgit.lib.PersonIdent}
+ * @param when
+ * local time
+ * @param tz
+ * time zone offset
+ * @since 7.1
+ */
+ public PersonIdent(PersonIdent pi, Instant when, ZoneId tz) {
this(pi.getName(), pi.getEmailAddress(), when, tz);
}
@@ -202,9 +245,12 @@ public class PersonIdent implements Serializable {
* original {@link org.eclipse.jgit.lib.PersonIdent}
* @param aWhen
* local time
+ * @deprecated Use the variant with an Instant instead
*/
+ @Deprecated(since = "7.1")
public PersonIdent(PersonIdent pi, Date aWhen) {
- this(pi.getName(), pi.getEmailAddress(), aWhen.getTime(), pi.tzOffset);
+ this(pi.getName(), pi.getEmailAddress(), aWhen.toInstant(),
+ pi.tzOffset);
}
/**
@@ -218,7 +264,7 @@ public class PersonIdent implements Serializable {
* @since 6.1
*/
public PersonIdent(PersonIdent pi, Instant aWhen) {
- this(pi.getName(), pi.getEmailAddress(), aWhen.toEpochMilli(), pi.tzOffset);
+ this(pi.getName(), pi.getEmailAddress(), aWhen, pi.tzOffset);
}
/**
@@ -230,11 +276,12 @@ public class PersonIdent implements Serializable {
* local time stamp
* @param aTZ
* time zone
+ * @deprecated Use the variant with Instant and ZoneId instead
*/
+ @Deprecated(since = "7.1")
public PersonIdent(final String aName, final String aEmailAddress,
final Date aWhen, final TimeZone aTZ) {
- this(aName, aEmailAddress, aWhen.getTime(), aTZ.getOffset(aWhen
- .getTime()) / (60 * 1000));
+ this(aName, aEmailAddress, aWhen.toInstant(), aTZ.toZoneId());
}
/**
@@ -252,10 +299,16 @@ public class PersonIdent implements Serializable {
*/
public PersonIdent(final String aName, String aEmailAddress, Instant aWhen,
ZoneId zoneId) {
- this(aName, aEmailAddress, aWhen.toEpochMilli(),
- TimeZone.getTimeZone(zoneId)
- .getOffset(aWhen
- .toEpochMilli()) / (60 * 1000));
+ if (aName == null)
+ throw new IllegalArgumentException(
+ JGitText.get().personIdentNameNonNull);
+ if (aEmailAddress == null)
+ throw new IllegalArgumentException(
+ JGitText.get().personIdentEmailNonNull);
+ name = aName;
+ emailAddress = aEmailAddress;
+ when = aWhen;
+ tzOffset = zoneId;
}
/**
@@ -267,15 +320,18 @@ public class PersonIdent implements Serializable {
* local time stamp
* @param aTZ
* time zone
+ * @deprecated Use the variant with Instant and ZoneId instead
*/
+ @Deprecated(since = "7.1")
public PersonIdent(PersonIdent pi, long aWhen, int aTZ) {
- this(pi.getName(), pi.getEmailAddress(), aWhen, aTZ);
+ this(pi.getName(), pi.getEmailAddress(), Instant.ofEpochMilli(aWhen),
+ getZoneId(aTZ));
}
private PersonIdent(final String aName, final String aEmailAddress,
- long when) {
+ Instant when) {
this(aName, aEmailAddress, when, SystemReader.getInstance()
- .getTimezone(when));
+ .getTimeZoneAt(when));
}
private PersonIdent(UserConfig config) {
@@ -298,19 +354,12 @@ public class PersonIdent implements Serializable {
* local time stamp
* @param aTZ
* time zone
+ * @deprecated Use the variant with Instant and ZoneId instead
*/
+ @Deprecated(since = "7.1")
public PersonIdent(final String aName, final String aEmailAddress,
final long aWhen, final int aTZ) {
- if (aName == null)
- throw new IllegalArgumentException(
- JGitText.get().personIdentNameNonNull);
- if (aEmailAddress == null)
- throw new IllegalArgumentException(
- JGitText.get().personIdentEmailNonNull);
- name = aName;
- emailAddress = aEmailAddress;
- when = aWhen;
- tzOffset = aTZ;
+ this(aName, aEmailAddress, Instant.ofEpochMilli(aWhen), getZoneId(aTZ));
}
/**
@@ -335,9 +384,12 @@ public class PersonIdent implements Serializable {
* Get timestamp
*
* @return timestamp
+ *
+ * @deprecated Use getWhenAsInstant instead
*/
+ @Deprecated(since = "7.1")
public Date getWhen() {
- return new Date(when);
+ return Date.from(when);
}
/**
@@ -347,16 +399,19 @@ public class PersonIdent implements Serializable {
* @since 6.1
*/
public Instant getWhenAsInstant() {
- return Instant.ofEpochMilli(when);
+ return when;
}
/**
* Get this person's declared time zone
*
* @return this person's declared time zone; null if time zone is unknown.
+ *
+ * @deprecated Use getZoneId instead
*/
+ @Deprecated(since = "7.1")
public TimeZone getTimeZone() {
- return getTimeZone(tzOffset);
+ return TimeZone.getTimeZone(tzOffset);
}
/**
@@ -366,7 +421,17 @@ public class PersonIdent implements Serializable {
* @since 6.1
*/
public ZoneId getZoneId() {
- return getTimeZone().toZoneId();
+ return tzOffset;
+ }
+
+ /**
+ * Return the offset in this timezone at the specific time
+ *
+ * @return the offset
+ * @since 7.1
+ */
+ public ZoneOffset getZoneOffset() {
+ return tzOffset.getRules().getOffset(when);
}
/**
@@ -374,9 +439,11 @@ public class PersonIdent implements Serializable {
*
* @return this person's declared time zone as minutes east of UTC. If the
* timezone is to the west of UTC it is negative.
+ * @deprecated Use {@link #getZoneOffset()} and read minutes from there
*/
+ @Deprecated(since = "7.1")
public int getTimeZoneOffset() {
- return tzOffset;
+ return getZoneOffset().getTotalSeconds() / 60;
}
/**
@@ -388,18 +455,19 @@ public class PersonIdent implements Serializable {
public int hashCode() {
int hc = getEmailAddress().hashCode();
hc *= 31;
- hc += (int) (when / 1000L);
+ hc += when.hashCode();
return hc;
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object o) {
if (o instanceof PersonIdent) {
final PersonIdent p = (PersonIdent) o;
return getName().equals(p.getName())
&& getEmailAddress().equals(p.getEmailAddress())
- && when / 1000L == p.when / 1000L;
+ // commmit timestamps are stored with 1 second precision
+ && when.truncatedTo(ChronoUnit.SECONDS)
+ .equals(p.when.truncatedTo(ChronoUnit.SECONDS));
}
return false;
}
@@ -415,30 +483,26 @@ public class PersonIdent implements Serializable {
r.append(" <"); //$NON-NLS-1$
appendSanitized(r, getEmailAddress());
r.append("> "); //$NON-NLS-1$
- r.append(when / 1000);
+ r.append(when.toEpochMilli() / 1000);
r.append(' ');
- appendTimezone(r, tzOffset);
+ r.append(OFFSET_FORMATTER.format(getZoneOffset()));
return r.toString();
}
- /** {@inheritDoc} */
@Override
@SuppressWarnings("nls")
public String toString() {
final StringBuilder r = new StringBuilder();
- final SimpleDateFormat dtfmt;
- dtfmt = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy Z", Locale.US);
- dtfmt.setTimeZone(getTimeZone());
-
+ DateTimeFormatter dtfmt = DateTimeFormatter
+ .ofPattern("EEE MMM d HH:mm:ss yyyy Z", Locale.US) //$NON-NLS-1$
+ .withZone(tzOffset);
r.append("PersonIdent[");
r.append(getName());
r.append(", ");
r.append(getEmailAddress());
r.append(", ");
- r.append(dtfmt.format(Long.valueOf(when)));
+ r.append(dtfmt.format(when));
r.append("]");
-
return r.toString();
}
}
-
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java
index a4cd1d6894..b036a0b6a1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java
@@ -17,7 +17,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
import org.eclipse.jgit.lib.RebaseTodoLine.Action;
@@ -55,13 +55,14 @@ public class RebaseTodoFile {
* <code>true</code> if also comments should be reported
* @return the list of steps
* @throws java.io.IOException
+ * if an IO error occurred
*/
public List<RebaseTodoLine> readRebaseTodo(String path,
boolean includeComments) throws IOException {
byte[] buf = IO.readFully(new File(repo.getDirectory(), path));
int ptr = 0;
int tokenBegin = 0;
- List<RebaseTodoLine> r = new LinkedList<>();
+ List<RebaseTodoLine> r = new ArrayList<>();
while (ptr < buf.length) {
tokenBegin = ptr;
ptr = RawParseUtils.nextLF(buf, ptr);
@@ -126,8 +127,11 @@ public class RebaseTodoFile {
* Skip leading space, tab, CR and LF characters
*
* @param buf
+ * byte buffer
* @param tokenBegin
+ * index of token begin
* @param lineEnd
+ * index of line end
* @return the token within the range of the given {@code buf} that doesn't
* need to be skipped, {@code -1} if no such token found within the
* range (i.e. empty line)
@@ -193,6 +197,7 @@ public class RebaseTodoFile {
* @param append
* whether to append to an existing file or to write a new file
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps,
boolean append) throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java
index 8b778497fc..082b324d1a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java
@@ -57,6 +57,8 @@ public class RebaseTodoLine {
}
/**
+ * Get full action token name
+ *
* @return full action token name
*/
public String toToken() {
@@ -69,7 +71,10 @@ public class RebaseTodoLine {
}
/**
+ * Parse a token
+ *
* @param token
+ * token to parse
* @return the Action
*/
public static Action parse(String token) {
@@ -245,7 +250,6 @@ public class RebaseTodoLine {
return comment;
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
index 218c0a8e8e..9c374760b0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
@@ -78,6 +78,8 @@ public interface Ref {
}
/**
+ * Whether this storage has a loose file
+ *
* @return true if this storage has a loose file.
*/
public boolean isLoose() {
@@ -85,6 +87,8 @@ public interface Ref {
}
/**
+ * Whether this storage is inside the packed file
+ *
* @return true if this storage is inside the packed file.
*/
public boolean isPacked() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java
index 3ab649341f..c4c0001d3e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefComparator.java
@@ -27,7 +27,6 @@ public class RefComparator implements Comparator<Ref> {
/** Singleton instance of RefComparator */
public static final RefComparator INSTANCE = new RefComparator();
- /** {@inheritDoc} */
@Override
public int compare(Ref o1, Ref o2) {
return compareTo(o1, o2);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
index 98089fb8fd..49d5224325 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -26,6 +26,7 @@ import java.util.stream.Stream;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.api.PackRefsCommand;
/**
* Abstraction of name to {@link org.eclipse.jgit.lib.ObjectId} mapping.
@@ -136,6 +137,7 @@ public abstract class RefDatabase {
* with the passed ref name; empty collection when there are no
* conflicts
* @throws java.io.IOException
+ * if an error occurred
* @since 2.3
* @see #isNameConflicting(String)
*/
@@ -159,7 +161,7 @@ public abstract class RefDatabase {
if (existing.startsWith(prefix))
conflicting.add(existing);
- return conflicting;
+ return Collections.unmodifiableList(conflicting);
}
/**
@@ -237,23 +239,6 @@ public abstract class RefDatabase {
}
/**
- * Compatibility synonym for {@link #findRef(String)}.
- *
- * @param name
- * the name of the reference. May be a short name which must be
- * searched for using the standard {@link #SEARCH_PATH}.
- * @return the reference (if it exists); else {@code null}.
- * @throws IOException
- * the reference space cannot be accessed.
- * @deprecated Use {@link #findRef(String)} instead.
- */
- @Deprecated
- @Nullable
- public final Ref getRef(String name) throws IOException {
- return findRef(name);
- }
-
- /**
* Read a single reference.
* <p>
* Aside from taking advantage of {@link #SEARCH_PATH}, this method may be
@@ -371,6 +356,40 @@ public abstract class RefDatabase {
}
/**
+ * Get the reflog reader
+ *
+ * @param refName
+ * a {@link java.lang.String} object.
+ * @return a {@link org.eclipse.jgit.lib.ReflogReader} for the supplied
+ * refname, or {@code null} if the named ref does not exist.
+ * @throws java.io.IOException
+ * the ref could not be accessed.
+ * @since 7.2
+ */
+ @Nullable
+ public ReflogReader getReflogReader(String refName) throws IOException {
+ Ref ref = exactRef(refName);
+ if (ref == null) {
+ return null;
+ }
+ return getReflogReader(ref);
+ }
+
+ /**
+ * Get the reflog reader.
+ *
+ * @param ref
+ * a Ref
+ * @return a {@link org.eclipse.jgit.lib.ReflogReader} for the supplied ref.
+ * @throws IOException
+ * if an IO error occurred
+ * @since 7.2
+ */
+ @NonNull
+ public abstract ReflogReader getReflogReader(@NonNull Ref ref)
+ throws IOException;
+
+ /**
* Get a section of the reference namespace.
*
* @param prefix
@@ -440,7 +459,7 @@ public abstract class RefDatabase {
* @param excludes
* strings that names of refs can't start with; may be empty.
* @return immutable list of refs whose names start with {@code prefix} and
- * none of the strings in {@code exclude}.
+ * none of the strings in {@code excludes}.
* @throws java.io.IOException
* the reference space cannot be accessed.
* @since 5.11
@@ -609,4 +628,22 @@ public abstract class RefDatabase {
}
return null;
}
+
+ /**
+ * Optimize pack ref storage.
+ *
+ * @param pm
+ * a progress monitor
+ *
+ * @param packRefs
+ * {@link PackRefsCommand} to control ref packing behavior
+ *
+ * @throws java.io.IOException
+ * if an IO error occurred
+ * @since 7.1
+ */
+ public void packRefs(ProgressMonitor pm, PackRefsCommand packRefs)
+ throws IOException {
+ // nothing
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
index cdd4e4ba75..e8821d0cbb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java
@@ -122,6 +122,7 @@ public abstract class RefRename {
*
* @return the result of the new ref update
* @throws java.io.IOException
+ * if an IO error occurred
*/
public Result rename() throws IOException {
try {
@@ -138,6 +139,7 @@ public abstract class RefRename {
*
* @return the result of the rename operation.
* @throws java.io.IOException
+ * if an IO error occurred
*/
protected abstract Result doRename() throws IOException;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
index d1be63b966..d113243bea 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
@@ -115,7 +115,6 @@ public abstract class RefUpdate {
/**
* The ref was renamed from another name
- * <p>
*/
RENAMED,
@@ -245,6 +244,7 @@ public abstract class RefUpdate {
* a {@link org.eclipse.jgit.lib.RefUpdate.Result} object.
* @return {@code result}
* @throws java.io.IOException
+ * if an IO error occurred
*/
protected abstract Result doUpdate(Result desiredResult) throws IOException;
@@ -255,6 +255,7 @@ public abstract class RefUpdate {
* a {@link org.eclipse.jgit.lib.RefUpdate.Result} object.
* @return {@code result}
* @throws java.io.IOException
+ * if an IO error occurred
*/
protected abstract Result doDelete(Result desiredResult) throws IOException;
@@ -265,6 +266,7 @@ public abstract class RefUpdate {
* a {@link java.lang.String} object.
* @return {@link org.eclipse.jgit.lib.RefUpdate.Result#NEW} on success.
* @throws java.io.IOException
+ * if an IO error occurred
*/
protected abstract Result doLink(String target) throws IOException;
@@ -612,6 +614,7 @@ public abstract class RefUpdate {
*
* @return the result status of the delete.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public Result delete() throws IOException {
try (RevWalk rw = new RevWalk(getRepository())) {
@@ -628,6 +631,7 @@ public abstract class RefUpdate {
* the merge test. The walk will be reset to perform the test.
* @return the result status of the delete.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public Result delete(RevWalk walk) throws IOException {
final String myName = detachingSymbolicRef
@@ -668,6 +672,7 @@ public abstract class RefUpdate {
* @return {@link org.eclipse.jgit.lib.RefUpdate.Result#NEW} or
* {@link org.eclipse.jgit.lib.RefUpdate.Result#FORCED} on success.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public Result link(String target) throws IOException {
if (!target.startsWith(Constants.R_REFS))
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
index d2c3f9de68..41917f8b57 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
@@ -177,6 +177,7 @@ public abstract class RefWriter {
* @param content
* byte content of file to be written.
* @throws java.io.IOException
+ * if an IO error occurred
*/
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
index 1e6a661572..5233bfc109 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ReflogReader.java
@@ -25,6 +25,7 @@ public interface ReflogReader {
*
* @return the latest reflog entry, or null if no log
* @throws java.io.IOException
+ * if an IO error occurred
*/
ReflogEntry getLastEntry() throws IOException;
@@ -33,6 +34,7 @@ public interface ReflogReader {
*
* @return all reflog entries in reverse order
* @throws java.io.IOException
+ * if an IO error occurred
*/
List<ReflogEntry> getReverseEntries() throws IOException;
@@ -40,9 +42,11 @@ public interface ReflogReader {
* Get specific entry in the reflog relative to the last entry which is
* considered entry zero.
*
- * @param number a int.
+ * @param number
+ * a int.
* @return reflog entry or null if not found
* @throws java.io.IOException
+ * if an IO error occurred
*/
ReflogEntry getReverseEntry(int number) throws IOException;
@@ -53,6 +57,7 @@ public interface ReflogReader {
* max number of entries to read
* @return all reflog entries in reverse order
* @throws java.io.IOException
+ * if an IO error occurred
*/
List<ReflogEntry> getReverseEntries(int max) throws IOException;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
index db2571c673..c9dc6da4ba 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -15,8 +15,8 @@
package org.eclipse.jgit.lib;
-import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.LOCK_SUFFIX;
import java.io.BufferedOutputStream;
import java.io.File;
@@ -26,17 +26,21 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
import java.text.MessageFormat;
+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.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import org.eclipse.jgit.annotations.NonNull;
@@ -113,9 +117,12 @@ public abstract class Repository implements AutoCloseable {
final AtomicLong closedAt = new AtomicLong();
- /** Metadata directory holding the repository's critical files. */
+ /** $GIT_DIR: metadata directory holding the repository's critical files. */
private final File gitDir;
+ /** $GIT_COMMON_DIR: metadata directory holding the common repository's critical files. */
+ private final File gitCommonDir;
+
/** File abstraction used to resolve paths. */
private final FS fs;
@@ -129,6 +136,8 @@ public abstract class Repository implements AutoCloseable {
private final String initialBranch;
+ private final AtomicReference<Boolean> caseInsensitiveWorktree = new AtomicReference<>();
+
/**
* Initialize a new repository instance.
*
@@ -137,6 +146,7 @@ public abstract class Repository implements AutoCloseable {
*/
protected Repository(BaseRepositoryBuilder options) {
gitDir = options.getGitDir();
+ gitCommonDir = options.getGitCommonDir();
fs = options.getFS();
workTree = options.getWorkTree();
indexFile = options.getIndexFile();
@@ -175,6 +185,7 @@ public abstract class Repository implements AutoCloseable {
* the same as {@code create(false)}.
*
* @throws java.io.IOException
+ * if an IO error occurred
* @see #create(boolean)
*/
public void create() throws IOException {
@@ -219,6 +230,16 @@ public abstract class Repository implements AutoCloseable {
public abstract String getIdentifier();
/**
+ * Get common dir.
+ *
+ * @return $GIT_COMMON_DIR: local common metadata directory;
+ * @since 7.0
+ */
+ public File getCommonDirectory() {
+ return gitCommonDir;
+ }
+
+ /**
* Get the object database which stores this repository's data.
*
* @return the object database which stores this repository's data.
@@ -292,25 +313,6 @@ public abstract class Repository implements AutoCloseable {
}
/**
- * Whether the specified object is stored in this repo or any of the known
- * shared repositories.
- *
- * @param objectId
- * a {@link org.eclipse.jgit.lib.AnyObjectId} object.
- * @return true if the specified object is stored in this repo or any of the
- * known shared repositories.
- * @deprecated use {@code getObjectDatabase().has(objectId)}
- */
- @Deprecated
- public boolean hasObject(AnyObjectId objectId) {
- try {
- return getObjectDatabase().has(objectId);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
-
- /**
* Open an object from this repository.
* <p>
* This is a one-shot call interface which may be faster than allocating a
@@ -484,11 +486,14 @@ public abstract class Repository implements AutoCloseable {
* Thus this method can be used to process an expression to a method that
* expects a branch or revision id.
*
- * @param revstr a {@link java.lang.String} object.
+ * @param revstr
+ * a {@link java.lang.String} object.
* @return object id or ref name from resolved expression or {@code null} if
* given expression cannot be resolved
* @throws org.eclipse.jgit.errors.AmbiguousObjectException
+ * if a shortened ObjectId was ambiguous
* @throws java.io.IOException
+ * if an IO error occurred
*/
@Nullable
public String simplify(String revstr)
@@ -968,7 +973,6 @@ public abstract class Repository implements AutoCloseable {
getRefDatabase().close();
}
- /** {@inheritDoc} */
@Override
@NonNull
public String toString() {
@@ -999,6 +1003,7 @@ public abstract class Repository implements AutoCloseable {
* {@code null} if the repository is corrupt and has no HEAD
* reference.
* @throws java.io.IOException
+ * if an IO error occurred
*/
@Nullable
public String getFullBranch() throws IOException {
@@ -1027,6 +1032,7 @@ public abstract class Repository implements AutoCloseable {
* in hex format if the current branch is detached, or {@code null}
* if the repository is corrupt and has no HEAD reference.
* @throws java.io.IOException
+ * if an IO error occurred
*/
@Nullable
public String getBranch() throws IOException {
@@ -1056,6 +1062,7 @@ public abstract class Repository implements AutoCloseable {
*
* @return unmodifiable collection of other known objects.
* @throws IOException
+ * if an IO error occurred
*/
@NonNull
public Set<ObjectId> getAdditionalHaves() throws IOException {
@@ -1066,11 +1073,12 @@ public abstract class Repository implements AutoCloseable {
* Get a ref by name.
*
* @param name
- * the name of the ref to lookup. Must not be a short-hand
- * form; e.g., "master" is not automatically expanded to
+ * the name of the ref to lookup. Must not be a short-hand form;
+ * e.g., "master" is not automatically expanded to
* "refs/heads/master".
* @return the Ref with the given name, or {@code null} if it does not exist
* @throws java.io.IOException
+ * if an IO error occurred
* @since 4.2
*/
@Nullable
@@ -1087,6 +1095,7 @@ public abstract class Repository implements AutoCloseable {
* "refs/heads/master" if "refs/heads/master" already exists.
* @return the Ref with the given name, or {@code null} if it does not exist
* @throws java.io.IOException
+ * if an IO error occurred
* @since 4.2
*/
@Nullable
@@ -1142,11 +1151,9 @@ public abstract class Repository implements AutoCloseable {
* new Ref object representing the same data as Ref, but isPeeled()
* will be true and getPeeledObjectId will contain the peeled object
* (or null).
- * @deprecated use {@code getRefDatabase().peel(ref)} instead.
*/
- @Deprecated
@NonNull
- public Ref peel(Ref ref) {
+ private Ref peel(Ref ref) {
try {
return getRefDatabase().peel(ref);
} catch (IOException e) {
@@ -1162,6 +1169,7 @@ public abstract class Repository implements AutoCloseable {
*
* @return a map with all objects referenced by a peeled ref.
* @throws IOException
+ * if an IO error occurred
*/
@NonNull
public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId()
@@ -1575,10 +1583,45 @@ public abstract class Repository implements AutoCloseable {
}
/**
+ * Tells whether the work tree is on a case-insensitive file system.
+ *
+ * @return {@code true} if the work tree is case-insensitive; {@code false}
+ * otherwise
+ * @throws NoWorkTreeException
+ * if the repository is bare
+ * @since 7.2
+ */
+ public boolean isWorkTreeCaseInsensitive() throws NoWorkTreeException {
+ Boolean flag = caseInsensitiveWorktree.get();
+ if (flag == null) {
+ File directory = getWorkTree();
+ // See if we can find ".git" also as ".GIT".
+ File dotGit = new File(directory, Constants.DOT_GIT);
+ if (Files.exists(dotGit.toPath(), LinkOption.NOFOLLOW_LINKS)) {
+ dotGit = new File(directory,
+ Constants.DOT_GIT.toUpperCase(Locale.ROOT));
+ flag = Boolean.valueOf(Files.exists(dotGit.toPath(),
+ LinkOption.NOFOLLOW_LINKS));
+ } else {
+ // Fall back to a mostly sane default. On Mac, HFS+ and APFS
+ // partitions are case-insensitive by default but can be
+ // configured to be case-sensitive.
+ SystemReader system = SystemReader.getInstance();
+ flag = Boolean.valueOf(system.isWindows() || system.isMacOS());
+ }
+ if (!caseInsensitiveWorktree.compareAndSet(null, flag)) {
+ flag = caseInsensitiveWorktree.get();
+ }
+ }
+ return flag.booleanValue();
+ }
+
+ /**
* Force a scan for changed refs. Fires an IndexChangedEvent(false) if
* changes are detected.
*
* @throws java.io.IOException
+ * if an IO error occurred
*/
public abstract void scanForRepoChanges() throws IOException;
@@ -1689,10 +1732,13 @@ public abstract class Repository implements AutoCloseable {
* @throws java.io.IOException
* the ref could not be accessed.
* @since 3.0
+ * @deprecated use {@code #getRefDatabase().getReflogReader(String)} instead
*/
+ @Deprecated(since = "7.2")
@Nullable
- public abstract ReflogReader getReflogReader(String refName)
- throws IOException;
+ public ReflogReader getReflogReader(String refName) throws IOException {
+ return getRefDatabase().getReflogReader(refName);
+ }
/**
* Get the reflog reader. Subclasses should override this method and provide
@@ -1700,14 +1746,17 @@ public abstract class Repository implements AutoCloseable {
*
* @param ref
* a Ref
- * @return a {@link org.eclipse.jgit.lib.ReflogReader} for the supplied ref,
- * or {@code null} if the ref does not exist.
+ * @return a {@link org.eclipse.jgit.lib.ReflogReader} for the supplied ref.
* @throws IOException
+ * if an IO error occurred
* @since 5.13.2
+ * @deprecated use {@code #getRefDatabase().getReflogReader(Ref)} instead
*/
- public @Nullable ReflogReader getReflogReader(@NonNull Ref ref)
+ @Deprecated(since = "7.2")
+ @NonNull
+ public ReflogReader getReflogReader(@NonNull Ref ref)
throws IOException {
- return getReflogReader(ref.getName());
+ return getRefDatabase().getReflogReader(ref);
}
/**
@@ -1718,6 +1767,7 @@ public abstract class Repository implements AutoCloseable {
* @return a String containing the content of the MERGE_MSG file or
* {@code null} if this file doesn't exist
* @throws java.io.IOException
+ * if an IO error occurred
* @throws org.eclipse.jgit.errors.NoWorkTreeException
* if this is bare, which implies it has no working directory.
* See {@link #isBare()}.
@@ -1737,6 +1787,7 @@ public abstract class Repository implements AutoCloseable {
* the message which should be written or <code>null</code> to
* delete the file
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void writeMergeCommitMsg(String msg) throws IOException {
File mergeMsgFile = new File(gitDir, Constants.MERGE_MSG);
@@ -1751,6 +1802,7 @@ public abstract class Repository implements AutoCloseable {
* @return a String containing the content of the COMMIT_EDITMSG file or
* {@code null} if this file doesn't exist
* @throws java.io.IOException
+ * if an IO error occurred
* @throws org.eclipse.jgit.errors.NoWorkTreeException
* if this is bare, which implies it has no working directory.
* See {@link #isBare()}.
@@ -1770,6 +1822,7 @@ public abstract class Repository implements AutoCloseable {
* the message which should be written or {@code null} to delete
* the file
* @throws java.io.IOException
+ * if an IO error occurred
* @since 4.0
*/
public void writeCommitEditMsg(String msg) throws IOException {
@@ -1786,6 +1839,7 @@ public abstract class Repository implements AutoCloseable {
* {@code null} if this file doesn't exist. Also if the file exists
* but is empty {@code null} will be returned
* @throws java.io.IOException
+ * if an IO error occurred
* @throws org.eclipse.jgit.errors.NoWorkTreeException
* if this is bare, which implies it has no working directory.
* See {@link #isBare()}.
@@ -1799,7 +1853,7 @@ public abstract class Repository implements AutoCloseable {
if (raw == null)
return null;
- LinkedList<ObjectId> heads = new LinkedList<>();
+ List<ObjectId> heads = new ArrayList<>();
for (int p = 0; p < raw.length;) {
heads.add(ObjectId.fromString(raw, p));
p = RawParseUtils
@@ -1818,6 +1872,7 @@ public abstract class Repository implements AutoCloseable {
* a list of commits which IDs should be written to
* $GIT_DIR/MERGE_HEAD or <code>null</code> to delete the file
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void writeMergeHeads(List<? extends ObjectId> heads) throws IOException {
writeHeadsFile(heads, Constants.MERGE_HEAD);
@@ -1830,6 +1885,7 @@ public abstract class Repository implements AutoCloseable {
* doesn't exist. Also if the file exists but is empty {@code null}
* will be returned
* @throws java.io.IOException
+ * if an IO error occurred
* @throws org.eclipse.jgit.errors.NoWorkTreeException
* if this is bare, which implies it has no working directory.
* See {@link #isBare()}.
@@ -1854,6 +1910,7 @@ public abstract class Repository implements AutoCloseable {
* doesn't exist. Also if the file exists but is empty {@code null}
* will be returned
* @throws java.io.IOException
+ * if an IO error occurred
* @throws org.eclipse.jgit.errors.NoWorkTreeException
* if this is bare, which implies it has no working directory.
* See {@link #isBare()}.
@@ -1877,6 +1934,7 @@ public abstract class Repository implements AutoCloseable {
* an object id of the cherry commit or <code>null</code> to
* delete the file
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void writeCherryPickHead(ObjectId head) throws IOException {
List<ObjectId> heads = (head != null) ? Collections.singletonList(head)
@@ -1892,6 +1950,7 @@ public abstract class Repository implements AutoCloseable {
* an object id of the revert commit or <code>null</code> to
* delete the file
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void writeRevertHead(ObjectId head) throws IOException {
List<ObjectId> heads = (head != null) ? Collections.singletonList(head)
@@ -1906,6 +1965,7 @@ public abstract class Repository implements AutoCloseable {
* an object id of the original HEAD commit or <code>null</code>
* to delete the file
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void writeOrigHead(ObjectId head) throws IOException {
List<ObjectId> heads = head != null ? Collections.singletonList(head)
@@ -1920,6 +1980,7 @@ public abstract class Repository implements AutoCloseable {
* doesn't exist. Also if the file exists but is empty {@code null}
* will be returned
* @throws java.io.IOException
+ * if an IO error occurred
* @throws org.eclipse.jgit.errors.NoWorkTreeException
* if this is bare, which implies it has no working directory.
* See {@link #isBare()}.
@@ -1941,6 +2002,7 @@ public abstract class Repository implements AutoCloseable {
* @return a String containing the content of the SQUASH_MSG file or
* {@code null} if this file doesn't exist
* @throws java.io.IOException
+ * if an IO error occurred
* @throws NoWorkTreeException
* if this is bare, which implies it has no working directory.
* See {@link #isBare()}.
@@ -1960,6 +2022,7 @@ public abstract class Repository implements AutoCloseable {
* the message which should be written or <code>null</code> to
* delete the file
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void writeSquashCommitMsg(String msg) throws IOException {
File squashMsgFile = new File(gitDir, Constants.SQUASH_MSG);
@@ -1997,9 +2060,11 @@ public abstract class Repository implements AutoCloseable {
* Read a file from the git directory.
*
* @param filename
+ * the file to read
* @return the raw contents or {@code null} if the file doesn't exist or is
* empty
* @throws IOException
+ * if an IO error occurred
*/
private byte[] readGitDirectoryFile(String filename) throws IOException {
File file = new File(getDirectory(), filename);
@@ -2021,8 +2086,11 @@ public abstract class Repository implements AutoCloseable {
* a list of object ids to write or null if the file should be
* deleted.
* @param filename
+ * name of the file to write heads to
* @throws FileNotFoundException
+ * if the heads file couldn't be found
* @throws IOException
+ * if an IO error occurred
*/
private void writeHeadsFile(List<? extends ObjectId> heads, String filename)
throws FileNotFoundException, IOException {
@@ -2052,6 +2120,7 @@ public abstract class Repository implements AutoCloseable {
* <code>true</code> if also comments should be reported
* @return the list of steps
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.2
*/
@NonNull
@@ -2072,6 +2141,7 @@ public abstract class Repository implements AutoCloseable {
* @param append
* whether to append to an existing file or to write a new file
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.2
*/
public void writeRebaseTodoFile(String path, List<RebaseTodoLine> steps,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
index 41f291b57b..18366541da 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
@@ -382,6 +382,8 @@ public class RepositoryCache {
private final FS fs;
/**
+ * Create a pointer to a location on disk.
+ *
* @param directory
* exact location of the repository.
* @param fs
@@ -401,7 +403,11 @@ public class RepositoryCache {
}
}
- /** @return location supplied to the constructor. */
+ /**
+ * Get directory containing the repository database
+ *
+ * @return directory containing the repository database.
+ */
public final File getFile() {
return path;
}
@@ -444,10 +450,21 @@ public class RepositoryCache {
* Git directory.
*/
public static boolean isGitRepository(File dir, FS fs) {
- return fs.resolve(dir, Constants.OBJECTS).exists()
- && fs.resolve(dir, "refs").exists() //$NON-NLS-1$
- && (fs.resolve(dir, Constants.REFTABLE).exists()
- || isValidHead(new File(dir, Constants.HEAD)));
+ // check if common-dir available or fallback to git-dir
+ File commonDir;
+ try {
+ commonDir = fs.getCommonDir(dir);
+ } catch (IOException e) {
+ commonDir = null;
+ }
+ if (commonDir == null) {
+ commonDir = dir;
+ }
+ return fs.resolve(commonDir, Constants.OBJECTS).exists()
+ && fs.resolve(commonDir, "refs").exists() //$NON-NLS-1$
+ && (fs.resolve(commonDir, Constants.REFTABLE).exists()
+ || isValidHead(
+ new File(commonDir, Constants.HEAD)));
}
private static boolean isValidHead(File head) {
@@ -490,15 +507,31 @@ public class RepositoryCache {
* null if there is no suitable match.
*/
public static File resolve(File directory, FS fs) {
- if (isGitRepository(directory, fs))
+ // the folder itself
+ if (isGitRepository(directory, fs)) {
return directory;
- if (isGitRepository(new File(directory, Constants.DOT_GIT), fs))
- return new File(directory, Constants.DOT_GIT);
-
- final String name = directory.getName();
- final File parent = directory.getParentFile();
- if (isGitRepository(new File(parent, name + Constants.DOT_GIT_EXT), fs))
- return new File(parent, name + Constants.DOT_GIT_EXT);
+ }
+ // the .git subfolder or file (reference)
+ File dotDir = new File(directory, Constants.DOT_GIT);
+ if (dotDir.isFile()) {
+ try {
+ File refDir = BaseRepositoryBuilder.getSymRef(directory,
+ dotDir, fs);
+ if (refDir != null && isGitRepository(refDir, fs)) {
+ return refDir;
+ }
+ } catch (IOException ignored) {
+ // Continue searching if gitdir ref isn't found
+ }
+ } else if (isGitRepository(dotDir, fs)) {
+ return dotDir;
+ }
+ // the folder extended with .git (bare)
+ File bareDir = new File(directory.getParentFile(),
+ directory.getName() + Constants.DOT_GIT_EXT);
+ if (isGitRepository(bareDir, fs)) {
+ return bareDir;
+ }
return null;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java
index 98d719183d..eaa15fbc99 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryState.java
@@ -355,27 +355,37 @@ public enum RepositoryState {
public abstract boolean canCheckout();
/**
+ * Whether we can commit
+ *
* @return true if we can commit
*/
public abstract boolean canCommit();
/**
+ * Whether reset to another HEAD is considered SAFE
+ *
* @return true if reset to another HEAD is considered SAFE
*/
public abstract boolean canResetHead();
/**
+ * Whether amending is considered SAFE
+ *
* @return true if amending is considered SAFE
*/
public abstract boolean canAmend();
/**
+ * Whether a rebase is in progress
+ *
* @return true if the repository is currently in a rebase
* @since 3.0
*/
public abstract boolean isRebasing();
/**
+ * Get a human readable description of the state
+ *
* @return a human readable description of the state.
*/
public abstract String getDescription();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifier.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifier.java
new file mode 100644
index 0000000000..2ce2708cb9
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifier.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2021, 2024 Thomas Wolf <twolf@apache.org> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.util.Date;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+
+/**
+ * A {@code SignatureVerifier} can verify signatures on git commits and tags.
+ *
+ * @since 7.0
+ */
+public interface SignatureVerifier {
+
+ /**
+ * Verifies a given signature for given data.
+ *
+ * @param repository
+ * the {@link Repository} the data comes from.
+ * @param config
+ * the {@link GpgConfig}
+ * @param data
+ * the signature is for
+ * @param signatureData
+ * the ASCII-armored signature
+ * @return a {@link SignatureVerification} describing the outcome
+ * @throws IOException
+ * if the signature cannot be parsed
+ * @throws JGitInternalException
+ * if signature verification fails
+ */
+ SignatureVerification verify(@NonNull Repository repository,
+ @NonNull GpgConfig config, byte[] data, byte[] signatureData)
+ throws IOException;
+
+ /**
+ * Retrieves the name of this verifier. This should be a short string
+ * identifying the engine that verified the signature, like "gpg" if GPG is
+ * used, or "bc" for a BouncyCastle implementation.
+ *
+ * @return the name
+ */
+ @NonNull
+ String getName();
+
+ /**
+ * A {@link SignatureVerifier} may cache public keys to speed up
+ * verifying signatures on multiple objects. This clears this cache, if any.
+ */
+ void clear();
+
+ /**
+ * A {@code SignatureVerification} returns data about a (positively or
+ * negatively) verified signature.
+ *
+ * @param verifierName
+ * the name of the verifier that created this verification result
+ * @param creationDate
+ * date and time the signature was created
+ * @param signer
+ * the signer as stored in the signature, or {@code null} if
+ * unknown
+ * @param keyFingerprint
+ * fingerprint of the public key, or {@code null} if unknown
+ * @param keyUser
+ * user associated with the key, or {@code null} if unknown
+ * @param verified
+ * whether the signature verification was successful
+ * @param expired
+ * whether the public key used for this signature verification
+ * was expired when the signature was created
+ * @param trustLevel
+ * the trust level of the public key used to verify the signature
+ * @param message
+ * human-readable message giving additional information about the
+ * outcome of the verification, possibly {@code null}
+ */
+ record SignatureVerification(
+ String verifierName,
+ Date creationDate,
+ String signer,
+ String keyFingerprint,
+ String keyUser,
+ boolean verified,
+ boolean expired,
+ @NonNull TrustLevel trustLevel,
+ String message) {
+ }
+
+ /**
+ * The owner's trust in a public key.
+ */
+ enum TrustLevel {
+ UNKNOWN, NEVER, MARGINAL, FULL, ULTIMATE
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifierFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifierFactory.java
new file mode 100644
index 0000000000..7844aba3bd
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifierFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 Thomas Wolf <twolf@apache.org> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.lib;
+
+import org.eclipse.jgit.annotations.NonNull;
+
+/**
+ * A factory for {@link SignatureVerifier}s.
+ *
+ * @since 7.0
+ */
+public interface SignatureVerifierFactory {
+
+ /**
+ * Tells what kind of {@link SignatureVerifier} this factory creates.
+ *
+ * @return the {@link GpgConfig.GpgFormat} of the signer
+ */
+ @NonNull
+ GpgConfig.GpgFormat getType();
+
+ /**
+ * Creates a new instance of a {@link SignatureVerifier} that can produce
+ * signatures of type {@link #getType()}.
+ *
+ * @return a new {@link SignatureVerifier}
+ */
+ @NonNull
+ SignatureVerifier create();
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifiers.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifiers.java
new file mode 100644
index 0000000000..01c8422b66
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignatureVerifiers.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2024 Thomas Wolf <twolf@apache.org> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Manages the available signers.
+ *
+ * @since 7.0
+ */
+public final class SignatureVerifiers {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SignatureVerifiers.class);
+
+ private static final byte[] PGP_PREFIX = Constants.GPG_SIGNATURE_PREFIX
+ .getBytes(StandardCharsets.US_ASCII);
+
+ private static final byte[] X509_PREFIX = Constants.CMS_SIGNATURE_PREFIX
+ .getBytes(StandardCharsets.US_ASCII);
+
+ private static final byte[] SSH_PREFIX = Constants.SSH_SIGNATURE_PREFIX
+ .getBytes(StandardCharsets.US_ASCII);
+
+ private static final Map<GpgConfig.GpgFormat, SignatureVerifierFactory> FACTORIES = loadSignatureVerifiers();
+
+ private static final Map<GpgConfig.GpgFormat, SignatureVerifier> VERIFIERS = new ConcurrentHashMap<>();
+
+ private static Map<GpgConfig.GpgFormat, SignatureVerifierFactory> loadSignatureVerifiers() {
+ Map<GpgConfig.GpgFormat, SignatureVerifierFactory> result = new EnumMap<>(
+ GpgConfig.GpgFormat.class);
+ try {
+ for (SignatureVerifierFactory factory : ServiceLoader
+ .load(SignatureVerifierFactory.class)) {
+ GpgConfig.GpgFormat format = factory.getType();
+ SignatureVerifierFactory existing = result.get(format);
+ if (existing != null) {
+ LOG.warn("{}", //$NON-NLS-1$
+ MessageFormat.format(
+ JGitText.get().signatureServiceConflict,
+ "SignatureVerifierFactory", format, //$NON-NLS-1$
+ existing.getClass().getCanonicalName(),
+ factory.getClass().getCanonicalName()));
+ } else {
+ result.put(format, factory);
+ }
+ }
+ } catch (ServiceConfigurationError e) {
+ LOG.error(e.getMessage(), e);
+ }
+ return result;
+ }
+
+ private SignatureVerifiers() {
+ // No instantiation
+ }
+
+ /**
+ * Retrieves a {@link Signer} that can produce signatures of the given type
+ * {@code format}.
+ *
+ * @param format
+ * {@link GpgConfig.GpgFormat} the signer must support
+ * @return a {@link Signer}, or {@code null} if none is available
+ */
+ public static SignatureVerifier get(@NonNull GpgConfig.GpgFormat format) {
+ return VERIFIERS.computeIfAbsent(format, f -> {
+ SignatureVerifierFactory factory = FACTORIES.get(format);
+ if (factory == null) {
+ return null;
+ }
+ return factory.create();
+ });
+ }
+
+ /**
+ * Sets a specific signature verifier to use for a specific signature type.
+ *
+ * @param format
+ * signature type to set the {@code verifier} for
+ * @param verifier
+ * the {@link SignatureVerifier} to use for signatures of type
+ * {@code format}; if {@code null}, a default implementation, if
+ * available, may be used.
+ */
+ public static void set(@NonNull GpgConfig.GpgFormat format,
+ SignatureVerifier verifier) {
+ SignatureVerifier previous;
+ if (verifier == null) {
+ previous = VERIFIERS.remove(format);
+ } else {
+ previous = VERIFIERS.put(format, verifier);
+ }
+ if (previous != null) {
+ previous.clear();
+ }
+ }
+
+ /**
+ * Verifies the signature on a signed commit or tag.
+ *
+ * @param repository
+ * the {@link Repository} the object is from
+ * @param config
+ * the {@link GpgConfig} to use
+ * @param object
+ * to verify
+ * @return a {@link SignatureVerifier.SignatureVerification} describing the
+ * outcome of the verification, or {@code null} if the object does
+ * not have a signature of a known type
+ * @throws IOException
+ * if an error occurs getting a public key
+ * @throws org.eclipse.jgit.api.errors.JGitInternalException
+ * if signature verification fails
+ */
+ @Nullable
+ public static SignatureVerifier.SignatureVerification verify(
+ @NonNull Repository repository, @NonNull GpgConfig config,
+ @NonNull RevObject object) throws IOException {
+ if (object instanceof RevCommit) {
+ RevCommit commit = (RevCommit) object;
+ byte[] signatureData = commit.getRawGpgSignature();
+ if (signatureData == null) {
+ return null;
+ }
+ byte[] raw = commit.getRawBuffer();
+ // Now remove the GPG signature
+ byte[] header = { 'g', 'p', 'g', 's', 'i', 'g' };
+ int start = RawParseUtils.headerStart(header, raw, 0);
+ if (start < 0) {
+ return null;
+ }
+ int end = RawParseUtils.nextLfSkippingSplitLines(raw, start);
+ // start is at the beginning of the header's content
+ start -= header.length + 1;
+ // end is on the terminating LF; we need to skip that, too
+ if (end < raw.length) {
+ end++;
+ }
+ byte[] data = new byte[raw.length - (end - start)];
+ System.arraycopy(raw, 0, data, 0, start);
+ System.arraycopy(raw, end, data, start, raw.length - end);
+ return verify(repository, config, data, signatureData);
+ } else if (object instanceof RevTag) {
+ RevTag tag = (RevTag) object;
+ byte[] signatureData = tag.getRawGpgSignature();
+ if (signatureData == null) {
+ return null;
+ }
+ byte[] raw = tag.getRawBuffer();
+ // The signature is just tacked onto the end of the message, which
+ // is last in the buffer.
+ byte[] data = Arrays.copyOfRange(raw, 0,
+ raw.length - signatureData.length);
+ return verify(repository, config, data, signatureData);
+ }
+ return null;
+ }
+
+ /**
+ * Verifies a given signature for some give data.
+ *
+ * @param repository
+ * the {@link Repository} the object is from
+ * @param config
+ * the {@link GpgConfig} to use
+ * @param data
+ * to verify the signature of
+ * @param signature
+ * the given signature of the {@code data}
+ * @return a {@link SignatureVerifier.SignatureVerification} describing the
+ * outcome of the verification, or {@code null} if the signature
+ * type is unknown
+ * @throws IOException
+ * if an error occurs getting a public key
+ * @throws org.eclipse.jgit.api.errors.JGitInternalException
+ * if signature verification fails
+ */
+ @Nullable
+ public static SignatureVerifier.SignatureVerification verify(
+ @NonNull Repository repository, @NonNull GpgConfig config,
+ byte[] data, byte[] signature) throws IOException {
+ GpgConfig.GpgFormat format = getFormat(signature);
+ if (format == null) {
+ return null;
+ }
+ SignatureVerifier verifier = get(format);
+ if (verifier == null) {
+ return null;
+ }
+ return verifier.verify(repository, config, data, signature);
+ }
+
+ /**
+ * Determines the type of a given signature.
+ *
+ * @param signature
+ * to get the type of
+ * @return the signature type, or {@code null} if unknown
+ */
+ @Nullable
+ public static GpgConfig.GpgFormat getFormat(byte[] signature) {
+ if (RawParseUtils.match(signature, 0, PGP_PREFIX) > 0) {
+ return GpgConfig.GpgFormat.OPENPGP;
+ }
+ if (RawParseUtils.match(signature, 0, X509_PREFIX) > 0) {
+ return GpgConfig.GpgFormat.X509;
+ }
+ if (RawParseUtils.match(signature, 0, SSH_PREFIX) > 0) {
+ return GpgConfig.GpgFormat.SSH;
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signer.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signer.java
new file mode 100644
index 0000000000..3bb7464d08
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signer.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2024 Thomas Wolf <twolf@apache.org> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.lib;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.api.errors.CanceledException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException;
+import org.eclipse.jgit.transport.CredentialsProvider;
+
+/**
+ * Creates signatures for Git objects.
+ *
+ * @since 7.0
+ */
+public interface Signer {
+
+ /**
+ * Signs the specified object.
+ *
+ * <p>
+ * Implementors should obtain the payload for signing from the specified
+ * object via {@link ObjectBuilder#build()} and create a proper
+ * {@link GpgSignature}. The generated signature is set on the specified
+ * {@code object} (see {@link ObjectBuilder#setGpgSignature(GpgSignature)}).
+ * </p>
+ * <p>
+ * Any existing signature on the object must be discarded prior obtaining
+ * the payload via {@link ObjectBuilder#build()}.
+ * </p>
+ *
+ * @param repository
+ * {@link Repository} the object belongs to
+ * @param config
+ * GPG settings from the git config
+ * @param object
+ * the object to sign (must not be {@code null} and must be
+ * complete to allow proper calculation of payload)
+ * @param committer
+ * the signing identity (to help with key lookup in case signing
+ * key is not specified)
+ * @param signingKey
+ * if non-{@code null} overrides the signing key from the config
+ * @param credentialsProvider
+ * provider to use when querying for signing key credentials (eg.
+ * passphrase)
+ * @throws CanceledException
+ * when signing was canceled (eg., user aborted when entering
+ * passphrase)
+ * @throws IOException
+ * if an I/O error occurs
+ * @throws UnsupportedSigningFormatException
+ * if a config is given and the wanted key format is not
+ * supported
+ */
+ default void signObject(@NonNull Repository repository,
+ @NonNull GpgConfig config, @NonNull ObjectBuilder object,
+ @NonNull PersonIdent committer, String signingKey,
+ CredentialsProvider credentialsProvider)
+ throws CanceledException, IOException,
+ UnsupportedSigningFormatException {
+ try {
+ object.setGpgSignature(sign(repository, config, object.build(),
+ committer, signingKey, credentialsProvider));
+ } catch (UnsupportedEncodingException e) {
+ throw new JGitInternalException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Signs arbitrary data.
+ *
+ * @param repository
+ * {@link Repository} the signature is created in
+ * @param config
+ * GPG settings from the git config
+ * @param data
+ * the data to sign
+ * @param committer
+ * the signing identity (to help with key lookup in case signing
+ * key is not specified)
+ * @param signingKey
+ * if non-{@code null} overrides the signing key from the config
+ * @param credentialsProvider
+ * provider to use when querying for signing key credentials (eg.
+ * passphrase)
+ * @return the signature for {@code data}
+ * @throws CanceledException
+ * when signing was canceled (eg., user aborted when entering
+ * passphrase)
+ * @throws IOException
+ * if an I/O error occurs
+ * @throws UnsupportedSigningFormatException
+ * if a config is given and the wanted key format is not
+ * supported
+ */
+ GpgSignature sign(@NonNull Repository repository, @NonNull GpgConfig config,
+ byte[] data, @NonNull PersonIdent committer, String signingKey,
+ CredentialsProvider credentialsProvider) throws CanceledException,
+ IOException, UnsupportedSigningFormatException;
+
+ /**
+ * Indicates if a signing key is available for the specified committer
+ * and/or signing key.
+ *
+ * @param repository
+ * the current {@link Repository}
+ * @param config
+ * GPG settings from the git config
+ * @param committer
+ * the signing identity (to help with key lookup in case signing
+ * key is not specified)
+ * @param signingKey
+ * if non-{@code null} overrides the signing key from the config
+ * @param credentialsProvider
+ * provider to use when querying for signing key credentials (eg.
+ * passphrase)
+ * @return {@code true} if a signing key is available, {@code false}
+ * otherwise
+ * @throws CanceledException
+ * when signing was canceled (eg., user aborted when entering
+ * passphrase)
+ */
+ boolean canLocateSigningKey(@NonNull Repository repository,
+ @NonNull GpgConfig config, @NonNull PersonIdent committer,
+ String signingKey, CredentialsProvider credentialsProvider)
+ throws CanceledException;
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignerFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignerFactory.java
new file mode 100644
index 0000000000..125d25e3b7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SignerFactory.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 Thomas Wolf <twolf@apache.org> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.lib;
+
+import org.eclipse.jgit.annotations.NonNull;
+
+/**
+ * A factory for {@link Signer}s.
+ *
+ * @since 7.0
+ */
+public interface SignerFactory {
+
+ /**
+ * Tells what kind of {@link Signer} this factory creates.
+ *
+ * @return the {@link GpgConfig.GpgFormat} of the signer
+ */
+ @NonNull
+ GpgConfig.GpgFormat getType();
+
+ /**
+ * Creates a new instance of a {@link Signer} that can produce signatures of
+ * type {@link #getType()}.
+ *
+ * @return a new {@link Signer}
+ */
+ @NonNull
+ Signer create();
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signers.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signers.java
new file mode 100644
index 0000000000..7771b07841
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Signers.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 Thomas Wolf <twolf@apache.org> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.lib;
+
+import java.text.MessageFormat;
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.internal.JGitText;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Manages the available signers.
+ *
+ * @since 7.0
+ */
+public final class Signers {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Signers.class);
+
+ private static final Map<GpgConfig.GpgFormat, SignerFactory> SIGNER_FACTORIES = loadSigners();
+
+ private static final Map<GpgConfig.GpgFormat, Signer> SIGNERS = new ConcurrentHashMap<>();
+
+ private static Map<GpgConfig.GpgFormat, SignerFactory> loadSigners() {
+ Map<GpgConfig.GpgFormat, SignerFactory> result = new EnumMap<>(
+ GpgConfig.GpgFormat.class);
+ try {
+ for (SignerFactory factory : ServiceLoader
+ .load(SignerFactory.class)) {
+ GpgConfig.GpgFormat format = factory.getType();
+ SignerFactory existing = result.get(format);
+ if (existing != null) {
+ LOG.warn("{}", //$NON-NLS-1$
+ MessageFormat.format(
+ JGitText.get().signatureServiceConflict,
+ "SignerFactory", format, //$NON-NLS-1$
+ existing.getClass().getCanonicalName(),
+ factory.getClass().getCanonicalName()));
+ } else {
+ result.put(format, factory);
+ }
+ }
+ } catch (ServiceConfigurationError e) {
+ LOG.error(e.getMessage(), e);
+ }
+ return result;
+ }
+
+ private Signers() {
+ // No instantiation
+ }
+
+ /**
+ * Retrieves a {@link Signer} that can produce signatures of the given type
+ * {@code format}.
+ *
+ * @param format
+ * {@link GpgConfig.GpgFormat} the signer must support
+ * @return a {@link Signer}, or {@code null} if none is available
+ */
+ public static Signer get(@NonNull GpgConfig.GpgFormat format) {
+ return SIGNERS.computeIfAbsent(format, f -> {
+ SignerFactory factory = SIGNER_FACTORIES.get(format);
+ if (factory == null) {
+ return null;
+ }
+ return factory.create();
+ });
+ }
+
+ /**
+ * Sets a specific signer to use for a specific signature type.
+ *
+ * @param format
+ * signature type to set the {@code signer} for
+ * @param signer
+ * the {@link Signer} to use for signatures of type
+ * {@code format}; if {@code null}, a default implementation, if
+ * available, may be used.
+ */
+ public static void set(@NonNull GpgConfig.GpgFormat format, Signer signer) {
+ if (signer == null) {
+ SIGNERS.remove(format);
+ } else {
+ SIGNERS.put(format, signer);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredConfig.java
index 9ddfd59a18..a10eb0c583 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/StoredConfig.java
@@ -57,7 +57,6 @@ public abstract class StoredConfig extends Config {
*/
public abstract void save() throws IOException;
- /** {@inheritDoc} */
@Override
public void clear() {
super.clear();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
index 3ba33422c6..6fb8f326cc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
@@ -59,20 +59,17 @@ public class SymbolicRef implements Ref {
this.updateIndex = updateIndex;
}
- /** {@inheritDoc} */
@Override
@NonNull
public String getName() {
return name;
}
- /** {@inheritDoc} */
@Override
public boolean isSymbolic() {
return true;
}
- /** {@inheritDoc} */
@Override
@NonNull
public Ref getLeaf() {
@@ -82,35 +79,30 @@ public class SymbolicRef implements Ref {
return dst;
}
- /** {@inheritDoc} */
@Override
@NonNull
public Ref getTarget() {
return target;
}
- /** {@inheritDoc} */
@Override
@Nullable
public ObjectId getObjectId() {
return getLeaf().getObjectId();
}
- /** {@inheritDoc} */
@Override
@NonNull
public Storage getStorage() {
return Storage.LOOSE;
}
- /** {@inheritDoc} */
@Override
@Nullable
public ObjectId getPeeledObjectId() {
return getLeaf().getPeeledObjectId();
}
- /** {@inheritDoc} */
@Override
public boolean isPeeled() {
return getLeaf().isPeeled();
@@ -128,7 +120,6 @@ public class SymbolicRef implements Ref {
return updateIndex;
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java
index facb4a54be..ea73d95102 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java
@@ -206,24 +206,6 @@ public class TagBuilder extends ObjectBuilder {
return os.toByteArray();
}
- /**
- * Format this builder's state as an annotated tag object.
- *
- * @return this object in the canonical annotated tag format, suitable for
- * storage in a repository, or {@code null} if the tag cannot be
- * encoded
- * @deprecated since 5.11; use {@link #build()} instead
- */
- @Deprecated
- public byte[] toByteArray() {
- try {
- return build();
- } catch (UnsupportedEncodingException e) {
- return null;
- }
- }
-
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java
index 85aa0b6639..98ea2f97a3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TextProgressMonitor.java
@@ -45,7 +45,6 @@ public class TextProgressMonitor extends BatchingProgressMonitor {
this.write = true;
}
- /** {@inheritDoc} */
@Override
protected void onUpdate(String taskName, int workCurr, Duration duration) {
StringBuilder s = new StringBuilder();
@@ -53,7 +52,6 @@ public class TextProgressMonitor extends BatchingProgressMonitor {
send(s);
}
- /** {@inheritDoc} */
@Override
protected void onEndTask(String taskName, int workCurr, Duration duration) {
StringBuilder s = new StringBuilder();
@@ -73,7 +71,6 @@ public class TextProgressMonitor extends BatchingProgressMonitor {
appendDuration(s, duration);
}
- /** {@inheritDoc} */
@Override
protected void onUpdate(String taskName, int cmp, int totalWork, int pcnt,
Duration duration) {
@@ -82,7 +79,6 @@ public class TextProgressMonitor extends BatchingProgressMonitor {
send(s);
}
- /** {@inheritDoc} */
@Override
protected void onEndTask(String taskName, int cmp, int totalWork, int pcnt,
Duration duration) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java
index e553955560..05eacdcd63 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java
@@ -55,7 +55,6 @@ public class ThreadSafeProgressMonitor implements ProgressMonitor {
this.process = new Semaphore(0);
}
- /** {@inheritDoc} */
@Override
public void start(int totalTasks) {
if (!isMainThread())
@@ -63,7 +62,6 @@ public class ThreadSafeProgressMonitor implements ProgressMonitor {
pm.start(totalTasks);
}
- /** {@inheritDoc} */
@Override
public void beginTask(String title, int totalWork) {
if (!isMainThread())
@@ -132,14 +130,12 @@ public class ThreadSafeProgressMonitor implements ProgressMonitor {
pm.update(cnt);
}
- /** {@inheritDoc} */
@Override
public void update(int completed) {
if (0 == pendingUpdates.getAndAdd(completed))
process.release();
}
- /** {@inheritDoc} */
@Override
public boolean isCancelled() {
lock.lock();
@@ -150,7 +146,6 @@ public class ThreadSafeProgressMonitor implements ProgressMonitor {
}
}
- /** {@inheritDoc} */
@Override
public void endTask() {
if (!isMainThread())
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeFormatter.java
index e1f8684830..c3c170a2c9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeFormatter.java
@@ -332,7 +332,6 @@ public class TreeFormatter {
}
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java
index c4eb8f10d5..3d4e0d1f3c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TypedConfigGetter.java
@@ -17,6 +17,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.util.FS;
@@ -50,13 +51,40 @@ public interface TypedConfigGetter {
* default value to return if no value was present.
* @return true if any value or defaultValue is true, false for missing or
* explicit false
+ * @deprecated use
+ * {@link #getBoolean(Config, String, String, String, Boolean)}
+ * instead
*/
+ @Deprecated
boolean getBoolean(Config config, String section, String subsection,
String name, boolean defaultValue);
/**
+ * Get a boolean value from a git {@link Config}.
+ *
+ * @param config
+ * to get the value from
+ * @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
+ * @since 7.2
+ */
+ @Nullable
+ Boolean getBoolean(Config config, String section, String subsection,
+ String name, @Nullable Boolean defaultValue);
+
+ /**
* Parse an enumeration from a git {@link Config}.
*
+ * @param <T>
+ * type of the enum
* @param config
* to get the value from
* @param all
@@ -72,8 +100,9 @@ public interface TypedConfigGetter {
* default value to return if no value was present.
* @return the selected enumeration value, or {@code defaultValue}.
*/
+ @Nullable
<T extends Enum<?>> T getEnum(Config config, T[] all, String section,
- String subsection, String name, T defaultValue);
+ String subsection, String name, @Nullable T defaultValue);
/**
* Obtain an integer value from a git {@link Config}.
@@ -89,11 +118,34 @@ public interface TypedConfigGetter {
* @param defaultValue
* default value to return if no value was present.
* @return an integer value from the configuration, or defaultValue.
+ * @deprecated use {@link #getInt(Config, String, String, String, Integer)}
+ * instead
*/
+ @Deprecated
int getInt(Config config, String section, String subsection, String name,
int defaultValue);
/**
+ * Obtain an integer value from a git {@link Config}.
+ *
+ * @param config
+ * to get the value from
+ * @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.
+ * @since 7.2
+ */
+ @Nullable
+ Integer getInt(Config config, String section, String subsection,
+ String name, @Nullable Integer defaultValue);
+
+ /**
* Obtain an integer value from a git {@link Config} which must be in given
* range.
*
@@ -115,11 +167,43 @@ public interface TypedConfigGetter {
* @return an integer value from the configuration, or defaultValue.
* {@code #UNSET_INT} if unset.
* @since 6.1
+ * @deprecated use
+ * {@link #getIntInRange(Config, String, String, String, int, int, Integer)}
+ * instead
*/
+ @Deprecated
int getIntInRange(Config config, String section, String subsection,
String name, int minValue, int maxValue, int defaultValue);
/**
+ * Obtain an integer value from a git {@link Config} which must be in given
+ * range.
+ *
+ * @param config
+ * to get the value from
+ * @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 minValue
+ * minimal value
+ * @param maxValue
+ * maximum value
+ * @param defaultValue
+ * default value to return if no value was present. Use
+ * {@code #UNSET_INT} to set the default to unset.
+ * @return an integer value from the configuration, or defaultValue.
+ * {@code #UNSET_INT} if unset.
+ * @since 7.2
+ */
+ @Nullable
+ Integer getIntInRange(Config config, String section, String subsection,
+ String name, int minValue, int maxValue,
+ @Nullable Integer defaultValue);
+
+ /**
* Obtain a long value from a git {@link Config}.
*
* @param config
@@ -133,11 +217,34 @@ public interface TypedConfigGetter {
* @param defaultValue
* default value to return if no value was present.
* @return a long value from the configuration, or defaultValue.
+ * @deprecated use {@link #getLong(Config, String, String, String, Long)}
+ * instead
*/
+ @Deprecated
long getLong(Config config, String section, String subsection, String name,
long defaultValue);
/**
+ * Obtain a long value from a git {@link Config}.
+ *
+ * @param config
+ * to get the value from
+ * @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 a long value from the configuration, or defaultValue.
+ * @since 7.2
+ */
+ @Nullable
+ Long getLong(Config config, String section, String subsection, String name,
+ @Nullable Long defaultValue);
+
+ /**
* Parse a numerical time unit, such as "1 minute", from a git
* {@link Config}.
*
@@ -157,11 +264,41 @@ public interface TypedConfigGetter {
* indication of the units.
* @return the value, or {@code defaultValue} if not set, expressed in
* {@code units}.
+ * @deprecated use
+ * {@link #getTimeUnit(Config, String, String, String, Long, TimeUnit)}
+ * instead
*/
+ @Deprecated
long getTimeUnit(Config config, String section, String subsection,
String name, long defaultValue, TimeUnit wantUnit);
/**
+ * Parse a numerical time unit, such as "1 minute", from a git
+ * {@link Config}.
+ *
+ * @param config
+ * to get the value from
+ * @param section
+ * section the key is in.
+ * @param subsection
+ * subsection the key is in, or null if not in a subsection.
+ * @param name
+ * the key name.
+ * @param defaultValue
+ * default value to return if no value was present.
+ * @param wantUnit
+ * the units of {@code defaultValue} and the return value, as
+ * well as the units to assume if the value does not contain an
+ * indication of the units.
+ * @return the value, or {@code defaultValue} if not set, expressed in
+ * {@code units}.
+ * @since 7.2
+ */
+ @Nullable
+ Long getTimeUnit(Config config, String section, String subsection,
+ String name, @Nullable Long defaultValue, TimeUnit wantUnit);
+
+ /**
* Parse a string value from a git {@link Config} and treat it as a file
* path, replacing a ~/ prefix by the user's home directory.
* <p>
@@ -187,9 +324,10 @@ public interface TypedConfigGetter {
* @return the {@link Path}, or {@code defaultValue} if not set
* @since 5.10
*/
+ @Nullable
default Path getPath(Config config, String section, String subsection,
String name, @NonNull FS fs, File resolveAgainst,
- Path defaultValue) {
+ @Nullable Path defaultValue) {
String value = config.getString(section, subsection, name);
if (value == null) {
return defaultValue;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ContentMergeStrategy.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ContentMergeStrategy.java
index 6d568643d5..a835a1dfc5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ContentMergeStrategy.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ContentMergeStrategy.java
@@ -23,5 +23,12 @@ public enum ContentMergeStrategy {
OURS,
/** Resolve the conflict hunk using the theirs version. */
- THEIRS
-} \ No newline at end of file
+ THEIRS,
+
+ /**
+ * Resolve the conflict hunk using a union of both ours and theirs versions.
+ *
+ * @since 6.10.1
+ */
+ UNION
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java
index e44970abff..8857ef8571 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java
@@ -74,19 +74,21 @@ class EolAwareOutputStream extends OutputStream {
write('\n');
}
- /** @return true if a new line has just begun. */
+ /**
+ * Whether a new line has just begun
+ *
+ * @return true if a new line has just begun.
+ */
boolean isBeginln() {
return bol;
}
- /** {@inheritDoc} */
@Override
public void write(int val) throws IOException {
out.write(val);
bol = (val == '\n');
}
- /** {@inheritDoc} */
@Override
public void write(byte[] buf, int pos, int cnt) throws IOException {
if (cnt > 0) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java
index 80607351ae..d0d4d367b9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java
@@ -90,10 +90,16 @@ public final class MergeAlgorithm {
/**
* Does the three way merge between a common base and two sequences.
*
- * @param cmp comparison method for this execution.
- * @param base the common base sequence
- * @param ours the first sequence to be merged
- * @param theirs the second sequence to be merged
+ * @param <S>
+ * type of the sequences
+ * @param cmp
+ * comparison method for this execution.
+ * @param base
+ * the common base sequence
+ * @param ours
+ * the first sequence to be merged
+ * @param theirs
+ * the second sequence to be merged
* @return the resulting content
*/
public <S extends Sequence> MergeResult<S> merge(
@@ -114,6 +120,7 @@ public final class MergeAlgorithm {
result.add(1, 0, 0, ConflictState.NO_CONFLICT);
break;
case THEIRS:
+ case UNION:
result.add(2, 0, theirs.size(),
ConflictState.NO_CONFLICT);
break;
@@ -121,6 +128,8 @@ public final class MergeAlgorithm {
// Let their complete content conflict with empty text
result.add(1, 0, 0,
ConflictState.FIRST_CONFLICTING_RANGE);
+ result.add(0, 0, base.size(),
+ ConflictState.BASE_CONFLICTING_RANGE);
result.add(2, 0, theirs.size(),
ConflictState.NEXT_CONFLICTING_RANGE);
break;
@@ -140,6 +149,7 @@ public final class MergeAlgorithm {
// we modified, they deleted
switch (strategy) {
case OURS:
+ case UNION:
result.add(1, 0, ours.size(), ConflictState.NO_CONFLICT);
break;
case THEIRS:
@@ -149,6 +159,8 @@ public final class MergeAlgorithm {
// Let our complete content conflict with empty text
result.add(1, 0, ours.size(),
ConflictState.FIRST_CONFLICTING_RANGE);
+ result.add(0, 0, base.size(),
+ ConflictState.BASE_CONFLICTING_RANGE);
result.add(2, 0, 0, ConflictState.NEXT_CONFLICTING_RANGE);
break;
}
@@ -208,13 +220,18 @@ public final class MergeAlgorithm {
// set some initial values for the ranges in A and B which we
// want to handle
+ int oursBeginA = oursEdit.getBeginA();
+ int theirsBeginA = theirsEdit.getBeginA();
int oursBeginB = oursEdit.getBeginB();
int theirsBeginB = theirsEdit.getBeginB();
// harmonize the start of the ranges in A and B
if (oursEdit.getBeginA() < theirsEdit.getBeginA()) {
+ theirsBeginA -= theirsEdit.getBeginA()
+ - oursEdit.getBeginA();
theirsBeginB -= theirsEdit.getBeginA()
- oursEdit.getBeginA();
} else {
+ oursBeginA -= oursEdit.getBeginA() - theirsEdit.getBeginA();
oursBeginB -= oursEdit.getBeginA() - theirsEdit.getBeginA();
}
@@ -260,11 +277,15 @@ public final class MergeAlgorithm {
}
// harmonize the end of the ranges in A and B
+ int oursEndA = oursEdit.getEndA();
+ int theirsEndA = theirsEdit.getEndA();
int oursEndB = oursEdit.getEndB();
int theirsEndB = theirsEdit.getEndB();
if (oursEdit.getEndA() < theirsEdit.getEndA()) {
+ oursEndA += theirsEdit.getEndA() - oursEdit.getEndA();
oursEndB += theirsEdit.getEndA() - oursEdit.getEndA();
} else {
+ theirsEndA += oursEdit.getEndA() - theirsEdit.getEndA();
theirsEndB += oursEdit.getEndA() - theirsEdit.getEndA();
}
@@ -314,10 +335,27 @@ public final class MergeAlgorithm {
theirsEndB - commonSuffix,
ConflictState.NO_CONFLICT);
break;
+ case UNION:
+ result.add(1, oursBeginB + commonPrefix,
+ oursEndB - commonSuffix,
+ ConflictState.NO_CONFLICT);
+
+ result.add(2, theirsBeginB + commonPrefix,
+ theirsEndB - commonSuffix,
+ ConflictState.NO_CONFLICT);
+ break;
default:
result.add(1, oursBeginB + commonPrefix,
oursEndB - commonSuffix,
ConflictState.FIRST_CONFLICTING_RANGE);
+
+ int baseBegin = Math.min(oursBeginA, theirsBeginA)
+ + commonPrefix;
+ int baseEnd = Math.min(base.size(),
+ Math.max(oursEndA, theirsEndA)) - commonSuffix;
+ result.add(0, baseBegin, baseEnd,
+ ConflictState.BASE_CONFLICTING_RANGE);
+
result.add(2, theirsBeginB + commonPrefix,
theirsEndB - commonSuffix,
ConflictState.NEXT_CONFLICTING_RANGE);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeChunk.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeChunk.java
index ca998e30bc..7102aa6998 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeChunk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeChunk.java
@@ -29,14 +29,22 @@ public class MergeChunk {
NO_CONFLICT,
/**
- * This chunk does belong to a conflict and is the first one of the
+ * This chunk does belong to a conflict and is the ours section of the
* conflicting chunks
*/
FIRST_CONFLICTING_RANGE,
/**
- * This chunk does belong to a conflict but is not the first one of the
- * conflicting chunks. It's a subsequent one.
+ * This chunk does belong to a conflict and is the base section of the
+ * conflicting chunks
+ *
+ * @since 6.7
+ */
+ BASE_CONFLICTING_RANGE,
+
+ /**
+ * This chunk does belong to a conflict and is the theirs section of
+ * the conflicting chunks. It's a subsequent one.
*/
NEXT_CONFLICTING_RANGE
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
index 18b0ad92c8..079db4a07f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java
@@ -22,6 +22,7 @@ import org.eclipse.jgit.diff.RawText;
* A class to convert merge results into a Git conformant textual presentation
*/
public class MergeFormatter {
+
/**
* Formats the results of a merge of {@link org.eclipse.jgit.diff.RawText}
* objects in a Git conformant way. This method also assumes that the
@@ -39,27 +40,24 @@ public class MergeFormatter {
* name. This name is following the "&lt;&lt;&lt;&lt;&lt;&lt;&lt;
* " or "&gt;&gt;&gt;&gt;&gt;&gt;&gt; " conflict markers. The
* names for the sequences are given in this list
- * @param charsetName
- * the name of the character set used when writing conflict
- * metadata
+ * @param charset
+ * the character set used when writing conflict metadata
* @throws java.io.IOException
- * @deprecated Use
- * {@link #formatMerge(OutputStream, MergeResult, List, Charset)}
- * instead.
+ * if an IO error occurred
+ * @since 5.2
*/
- @Deprecated
public void formatMerge(OutputStream out, MergeResult<RawText> res,
- List<String> seqName, String charsetName) throws IOException {
- formatMerge(out, res, seqName, Charset.forName(charsetName));
+ List<String> seqName, Charset charset) throws IOException {
+ new MergeFormatterPass(out, res, seqName, charset).formatMerge();
}
/**
* Formats the results of a merge of {@link org.eclipse.jgit.diff.RawText}
- * objects in a Git conformant way. This method also assumes that the
- * {@link org.eclipse.jgit.diff.RawText} objects being merged are line
- * oriented files which use LF as delimiter. This method will also use LF to
- * separate chunks and conflict metadata, therefore it fits only to texts
- * that are LF-separated lines.
+ * objects in a Git conformant way using diff3 style. This method also
+ * assumes that the {@link org.eclipse.jgit.diff.RawText} objects being
+ * merged are line oriented files which use LF as delimiter. This method
+ * will also use LF to separate chunks and conflict metadata, therefore it
+ * fits only to texts that are LF-separated lines.
*
* @param out
* the output stream where to write the textual presentation
@@ -68,16 +66,18 @@ public class MergeFormatter {
* @param seqName
* When a conflict is reported each conflicting range will get a
* name. This name is following the "&lt;&lt;&lt;&lt;&lt;&lt;&lt;
- * " or "&gt;&gt;&gt;&gt;&gt;&gt;&gt; " conflict markers. The
- * names for the sequences are given in this list
+ * ", "|||||||" or "&gt;&gt;&gt;&gt;&gt;&gt;&gt; " conflict
+ * markers. The names for the sequences are given in this list
* @param charset
* the character set used when writing conflict metadata
* @throws java.io.IOException
- * @since 5.2
+ * if an IO error occurred
+ * @since 6.7
*/
- public void formatMerge(OutputStream out, MergeResult<RawText> res,
- List<String> seqName, Charset charset) throws IOException {
- new MergeFormatterPass(out, res, seqName, charset).formatMerge();
+ public void formatMergeDiff3(OutputStream out,
+ MergeResult<RawText> res, List<String> seqName, Charset charset)
+ throws IOException {
+ new MergeFormatterPass(out, res, seqName, charset, true).formatMerge();
}
/**
@@ -98,27 +98,29 @@ public class MergeFormatter {
* the name ranges from ours should get
* @param theirsName
* the name ranges from theirs should get
- * @param charsetName
- * the name of the character set used when writing conflict
- * metadata
+ * @param charset
+ * the character set used when writing conflict metadata
* @throws java.io.IOException
- * @deprecated use
- * {@link #formatMerge(OutputStream, MergeResult, String, String, String, Charset)}
- * instead.
+ * if an IO error occurred
+ * @since 5.2
*/
- @Deprecated
+ @SuppressWarnings("unchecked")
public void formatMerge(OutputStream out, MergeResult res, String baseName,
- String oursName, String theirsName, String charsetName) throws IOException {
- formatMerge(out, res, baseName, oursName, theirsName,
- Charset.forName(charsetName));
+ String oursName, String theirsName, Charset charset)
+ throws IOException {
+ List<String> names = new ArrayList<>(3);
+ names.add(baseName);
+ names.add(oursName);
+ names.add(theirsName);
+ formatMerge(out, res, names, charset);
}
/**
- * Formats the results of a merge of exactly two
- * {@link org.eclipse.jgit.diff.RawText} objects in a Git conformant way.
- * This convenience method accepts the names for the three sequences (base
- * and the two merged sequences) as explicit parameters and doesn't require
- * the caller to specify a List
+ * Formats the results of a merge of three
+ * {@link org.eclipse.jgit.diff.RawText} objects in a Git conformant way,
+ * using diff-3 style. This convenience method accepts the names for the
+ * three sequences (base and the two merged sequences) as explicit
+ * parameters and doesn't require the caller to specify a List
*
* @param out
* the {@link java.io.OutputStream} where to write the textual
@@ -134,16 +136,17 @@ public class MergeFormatter {
* @param charset
* the character set used when writing conflict metadata
* @throws java.io.IOException
- * @since 5.2
+ * if an IO error occurred
+ * @since 6.7
*/
@SuppressWarnings("unchecked")
- public void formatMerge(OutputStream out, MergeResult res, String baseName,
- String oursName, String theirsName, Charset charset)
- throws IOException {
+ public void formatMergeDiff3(OutputStream out,
+ MergeResult res, String baseName, String oursName,
+ String theirsName, Charset charset) throws IOException {
List<String> names = new ArrayList<>(3);
names.add(baseName);
names.add(oursName);
names.add(theirsName);
- formatMerge(out, res, names, charset);
+ formatMergeDiff3(out, res, names, charset);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
index f09b343007..5e80b5fb23 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java
@@ -31,6 +31,8 @@ class MergeFormatterPass {
private final boolean threeWayMerge;
+ private final boolean writeBase; // diff3-style requested
+
private String lastConflictingName; // is set to non-null whenever we are in
// a conflict
@@ -50,22 +52,47 @@ class MergeFormatterPass {
*/
MergeFormatterPass(OutputStream out, MergeResult<RawText> res,
List<String> seqName, Charset charset) {
+ this(out, res, seqName, charset, false);
+ }
+
+ /**
+ * @param out
+ * the {@link java.io.OutputStream} where to write the textual
+ * presentation
+ * @param res
+ * the merge result which should be presented
+ * @param seqName
+ * When a conflict is reported each conflicting range will get a
+ * name. This name is following the "&lt;&lt;&lt;&lt;&lt;&lt;&lt;
+ * ", "|||||||" or "&gt;&gt;&gt;&gt;&gt;&gt;&gt; " conflict
+ * markers. The names for the sequences are given in this list
+ * @param charset
+ * the character set used when writing conflict metadata
+ * @param writeBase
+ * base's contribution should be written in conflicts
+ */
+ MergeFormatterPass(OutputStream out, MergeResult<RawText> res,
+ List<String> seqName, Charset charset, boolean writeBase) {
this.out = new EolAwareOutputStream(out);
this.res = res;
this.seqName = seqName;
this.charset = charset;
this.threeWayMerge = (res.getSequences().size() == 3);
+ this.writeBase = writeBase;
}
void formatMerge() throws IOException {
boolean missingNewlineAtEnd = false;
for (MergeChunk chunk : res) {
- RawText seq = res.getSequences().get(chunk.getSequenceIndex());
- writeConflictMetadata(chunk);
- // the lines with conflict-metadata are written. Now write the chunk
- for (int i = chunk.getBegin(); i < chunk.getEnd(); i++)
- writeLine(seq, i);
- missingNewlineAtEnd = seq.isMissingNewlineAtEnd();
+ if (!isBase(chunk) || writeBase) {
+ RawText seq = res.getSequences().get(chunk.getSequenceIndex());
+ writeConflictMetadata(chunk);
+ // the lines with conflict-metadata are written. Now write the
+ // chunk
+ for (int i = chunk.getBegin(); i < chunk.getEnd(); i++)
+ writeLine(seq, i);
+ missingNewlineAtEnd = seq.isMissingNewlineAtEnd();
+ }
}
// one possible leftover: if the merge result ended with a conflict we
// have to close the last conflict here
@@ -77,16 +104,19 @@ class MergeFormatterPass {
private void writeConflictMetadata(MergeChunk chunk) throws IOException {
if (lastConflictingName != null
- && chunk.getConflictState() != ConflictState.NEXT_CONFLICTING_RANGE) {
- // found the end of an conflict
+ && !isTheirs(chunk) && !isBase(chunk)) {
+ // found the end of a conflict
writeConflictEnd();
}
- if (chunk.getConflictState() == ConflictState.FIRST_CONFLICTING_RANGE) {
- // found the start of an conflict
+ if (isOurs(chunk)) {
+ // found the start of a conflict
writeConflictStart(chunk);
- } else if (chunk.getConflictState() == ConflictState.NEXT_CONFLICTING_RANGE) {
- // found another conflicting chunk
+ } else if (isTheirs(chunk)) {
+ // found the theirs conflicting chunk
writeConflictChange(chunk);
+ } else if (isBase(chunk)) {
+ // found the base conflicting chunk
+ writeConflictBase(chunk);
}
}
@@ -113,6 +143,11 @@ class MergeFormatterPass {
+ lastConflictingName);
}
+ private void writeConflictBase(MergeChunk chunk) throws IOException {
+ lastConflictingName = seqName.get(chunk.getSequenceIndex());
+ writeln("||||||| " + lastConflictingName); //$NON-NLS-1$
+ }
+
private void writeln(String s) throws IOException {
out.beginln();
out.write((s + "\n").getBytes(charset)); //$NON-NLS-1$
@@ -125,4 +160,17 @@ class MergeFormatterPass {
if (out.isBeginln())
out.write('\n');
}
+
+ private boolean isBase(MergeChunk chunk) {
+ return chunk.getConflictState() == ConflictState.BASE_CONFLICTING_RANGE;
+ }
+
+ private boolean isOurs(MergeChunk chunk) {
+ return chunk
+ .getConflictState() == ConflictState.FIRST_CONFLICTING_RANGE;
+ }
+
+ private boolean isTheirs(MergeChunk chunk) {
+ return chunk.getConflictState() == ConflictState.NEXT_CONFLICTING_RANGE;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
index e0c083f55c..039d7d844c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
@@ -92,24 +92,6 @@ public class MergeMessageFormatter {
}
/**
- * Add section with conflicting paths to merge message. Lines are prefixed
- * with a hash.
- *
- * @param message
- * the original merge message
- * @param conflictingPaths
- * the paths with conflicts
- * @return merge message with conflicting paths added
- * @deprecated since 6.1; use
- * {@link #formatWithConflicts(String, Iterable, char)} instead
- */
- @Deprecated
- public String formatWithConflicts(String message,
- List<String> conflictingPaths) {
- return formatWithConflicts(message, conflictingPaths, '#');
- }
-
- /**
* Add section with conflicting paths to merge message.
*
* @param message
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java
index 20fc3c339c..acf955303d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeResult.java
@@ -97,7 +97,6 @@ public class MergeResult<S extends Sequence> implements Iterable<MergeChunk> {
static final ConflictState[] states = ConflictState.values();
- /** {@inheritDoc} */
@Override
public Iterator<MergeChunk> iterator() {
return new Iterator<>() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
index df6068925b..f58ef4faba 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java
@@ -18,10 +18,11 @@ package org.eclipse.jgit.merge;
import java.io.IOException;
import java.text.MessageFormat;
+import java.time.Instant;
+import java.time.ZoneOffset;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
-import java.util.TimeZone;
+import java.util.stream.Collectors;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -123,6 +124,7 @@ public class RecursiveMerger extends ResolveMerger {
* synthetic merge base this commit is visible only the merger's
* RevWalk and will not be in the repository.
* @throws java.io.IOException
+ * if an IO error occurred
* @throws IncorrectObjectTypeException
* one of the input objects is not a commit.
* @throws NoMergeBaseException
@@ -184,12 +186,15 @@ public class RecursiveMerger extends ResolveMerger {
if (mergeTrees(bcTree, currentBase.getTree(),
nextBase.getTree(), true))
currentBase = createCommitForTree(resultTree, parents);
- else
+ else {
+ String failedPaths = failingPathsMessage();
throw new NoMergeBaseException(
NoMergeBaseException.MergeBaseFailureReason.CONFLICTS_DURING_MERGE_BASE_CALCULATION,
MessageFormat.format(
JGitText.get().mergeRecursiveConflictsWhenMergingCommonAncestors,
- currentBase.getName(), nextBase.getName()));
+ currentBase.getName(), nextBase.getName(),
+ failedPaths));
+ }
}
} finally {
inCore = oldIncore;
@@ -213,6 +218,7 @@ public class RecursiveMerger extends ResolveMerger {
* the list of parent commits
* @return a new commit visible only within this merger's RevWalk.
* @throws IOException
+ * if an IO error occurred
*/
private RevCommit createCommitForTree(ObjectId tree, List<RevCommit> parents)
throws IOException {
@@ -227,11 +233,23 @@ public class RecursiveMerger extends ResolveMerger {
private static PersonIdent mockAuthor(List<RevCommit> parents) {
String name = RecursiveMerger.class.getSimpleName();
int time = 0;
- for (RevCommit p : parents)
+ for (RevCommit p : parents) {
time = Math.max(time, p.getCommitTime());
- return new PersonIdent(
- name, name + "@JGit", //$NON-NLS-1$
- new Date((time + 1) * 1000L),
- TimeZone.getTimeZone("GMT+0000")); //$NON-NLS-1$
+ }
+ return new PersonIdent(name, name + "@JGit", //$NON-NLS-1$
+ Instant.ofEpochSecond(time+1), ZoneOffset.UTC);
+ }
+
+ private String failingPathsMessage() {
+ int max = 25;
+ String failedPaths = failingPaths.entrySet().stream().limit(max)
+ .map(entry -> entry.getKey() + ":" + entry.getValue()) //$NON-NLS-1$
+ .collect(Collectors.joining("\n")); //$NON-NLS-1$
+
+ if (failingPaths.size() > max) {
+ failedPaths = String.format("%s\n... (%s failing paths omitted)", //$NON-NLS-1$
+ failedPaths, Integer.valueOf(failingPaths.size() - max));
+ }
+ return failedPaths;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
index e56513d4e9..dc96f65b87 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -3,8 +3,8 @@
* Copyright (C) 2010-2012, Matthias Sohn <matthias.sohn@sap.com>
* Copyright (C) 2012, Research In Motion Limited
* Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr)
- * Copyright (C) 2018, 2022 Thomas Wolf <twolf@apache.org>
- * Copyright (C) 2022, Google Inc. and others
+ * Copyright (C) 2018, 2023 Thomas Wolf <twolf@apache.org>
+ * Copyright (C) 2023, Google Inc. and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -32,7 +32,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -42,11 +41,13 @@ import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.attributes.Attribute;
import org.eclipse.jgit.attributes.Attributes;
+import org.eclipse.jgit.attributes.AttributesNodeProvider;
import org.eclipse.jgit.diff.DiffAlgorithm;
import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.diff.Sequence;
+import org.eclipse.jgit.dircache.Checkout;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuildIterator;
import org.eclipse.jgit.dircache.DirCacheBuilder;
@@ -79,7 +80,6 @@ import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
-import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.LfsFactory;
import org.eclipse.jgit.util.LfsFactory.LfsInputStream;
import org.eclipse.jgit.util.TemporaryBuffer;
@@ -106,13 +106,15 @@ public class ResolveMerger extends ThreeWayMerger {
*/
public static class Result {
- private final List<String> modifiedFiles = new LinkedList<>();
+ private final List<String> modifiedFiles = new ArrayList<>();
- private final List<String> failedToDelete = new LinkedList<>();
+ private final List<String> failedToDelete = new ArrayList<>();
private ObjectId treeId = null;
/**
+ * Get modified tree id if any
+ *
* @return Modified tree ID if any, or null otherwise.
*/
public ObjectId getTreeId() {
@@ -120,6 +122,8 @@ public class ResolveMerger extends ThreeWayMerger {
}
/**
+ * Get path of files that couldn't be deleted
+ *
* @return Files that couldn't be deleted.
*/
public List<String> getFailedToDelete() {
@@ -127,6 +131,8 @@ public class ResolveMerger extends ThreeWayMerger {
}
/**
+ * Get path of modified files
+ *
* @return Files modified during this operation.
*/
public List<String> getModifiedFiles() {
@@ -205,6 +211,12 @@ public class ResolveMerger extends ThreeWayMerger {
private boolean indexChangesWritten;
/**
+ * {@link Checkout} to use for actually checking out files if
+ * {@link #inCore} is {@code false}.
+ */
+ private Checkout checkout;
+
+ /**
* @param repo
* the {@link Repository}.
* @param dirCache
@@ -223,6 +235,7 @@ public class ResolveMerger extends ThreeWayMerger {
this.inCoreFileSizeLimit = getInCoreFileSizeLimit(config);
this.checkoutMetadataByPath = new HashMap<>();
this.cleanupMetadataByPath = new HashMap<>();
+ this.checkout = new Checkout(nonNullRepo(), workingTreeOptions);
}
/**
@@ -350,9 +363,8 @@ public class ResolveMerger extends ThreeWayMerger {
}
// All content operations are successfully done. If we can now write
- // the
- // new index we are on quite safe ground. Even if the checkout of
- // files coming from "theirs" fails the user can work around such
+ // the new index we are on quite safe ground. Even if the checkout
+ // of files coming from "theirs" fails the user can work around such
// failures by checking out the index again.
if (!builder.commit()) {
revertModifiedFiles();
@@ -466,7 +478,6 @@ public class ResolveMerger extends ThreeWayMerger {
/**
* Detects if CRLF conversion has been configured.
* <p>
- * </p>
* See {@link EolStreamTypeUtil#detectStreamType} for more info.
*
* @param attributes
@@ -518,14 +529,14 @@ public class ResolveMerger extends ThreeWayMerger {
for (Map.Entry<String, DirCacheEntry> entry : toBeCheckedOut
.entrySet()) {
DirCacheEntry dirCacheEntry = entry.getValue();
+ String gitPath = entry.getKey();
if (dirCacheEntry.getFileMode() == FileMode.GITLINK) {
- new File(nonNullRepo().getWorkTree(), entry.getKey())
- .mkdirs();
+ checkout.checkoutGitlink(dirCacheEntry, gitPath);
} else {
- DirCacheCheckout.checkoutEntry(repo, dirCacheEntry, reader,
- false, checkoutMetadataByPath.get(entry.getKey()),
- workingTreeOptions);
- result.modifiedFiles.add(entry.getKey());
+ checkout.checkout(dirCacheEntry,
+ checkoutMetadataByPath.get(gitPath), reader,
+ gitPath);
+ result.modifiedFiles.add(gitPath);
}
}
}
@@ -550,9 +561,8 @@ public class ResolveMerger extends ThreeWayMerger {
for (String path : result.modifiedFiles) {
DirCacheEntry entry = dirCache.getEntry(path);
if (entry != null) {
- DirCacheCheckout.checkoutEntry(repo, entry, reader, false,
- cleanupMetadataByPath.get(path),
- workingTreeOptions);
+ checkout.checkout(entry, cleanupMetadataByPath.get(path),
+ reader, path);
}
}
}
@@ -586,6 +596,8 @@ public class ResolveMerger extends ThreeWayMerger {
if (inCore) {
return;
}
+ checkout.safeCreateParentDirectory(path, file.getParentFile(),
+ false);
CheckoutMetadata metadata = new CheckoutMetadata(streamType,
smudgeCommand);
@@ -826,6 +838,13 @@ public class ResolveMerger extends ThreeWayMerger {
@NonNull
private ContentMergeStrategy contentStrategy = ContentMergeStrategy.CONFLICT;
+ /**
+ * The {@link AttributesNodeProvider} to use while merging trees.
+ *
+ * @since 6.10.1
+ */
+ protected AttributesNodeProvider attributesNodeProvider;
+
private static MergeAlgorithm getMergeAlgorithm(Config config) {
SupportedAlgorithm diffAlg = config.getEnum(
CONFIG_DIFF_SECTION, null, CONFIG_KEY_ALGORITHM,
@@ -904,7 +923,6 @@ public class ResolveMerger extends ThreeWayMerger {
: strategy;
}
- /** {@inheritDoc} */
@Override
protected boolean mergeImpl() throws IOException {
return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1],
@@ -915,18 +933,23 @@ public class ResolveMerger extends ThreeWayMerger {
* adds a new path with the specified stage to the index builder
*
* @param path
+ * the new path
* @param p
+ * canonical tree parser
* @param stage
- * @param lastMod
+ * the stage
+ * @param lastModified
+ * lastModified attribute of the file
* @param len
+ * file length
* @return the entry which was added to the index
*/
private DirCacheEntry add(byte[] path, CanonicalTreeParser p, int stage,
- Instant lastMod, long len) {
+ Instant lastModified, long len) {
if (p != null && !p.getEntryFileMode().equals(FileMode.TREE)) {
return workTreeUpdater.addExistingToIndex(p.getEntryObjectId(), path,
p.getEntryFileMode(), stage,
- lastMod, (int) len);
+ lastModified, (int) len);
}
return null;
}
@@ -1056,6 +1079,7 @@ public class ResolveMerger extends ThreeWayMerger {
* didn't match ours or the working-dir file was dirty and a
* conflict occurred
* @throws java.io.IOException
+ * if an IO error occurred
* @since 6.1
*/
protected boolean processEntry(CanonicalTreeParser base,
@@ -1257,10 +1281,22 @@ public class ResolveMerger extends ThreeWayMerger {
default:
break;
}
+ if (ignoreConflicts) {
+ // If the path is selected to be treated as binary via attributes, we do not perform
+ // content merge. When ignoreConflicts = true, we simply keep OURS to allow virtual commit
+ // to be built.
+ keep(ourDce);
+ return true;
+ }
+ // add the conflicting path to merge result
+ String currentPath = tw.getPathString();
+ MergeResult<RawText> result = new MergeResult<>(
+ Collections.emptyList());
+ result.setContainsConflicts(true);
+ mergeResults.put(currentPath, result);
addConflict(base, ours, theirs);
-
// attribute merge issues are conflicts but not failures
- unmergedPaths.add(tw.getPathString());
+ unmergedPaths.add(currentPath);
return true;
}
@@ -1272,38 +1308,52 @@ public class ResolveMerger extends ThreeWayMerger {
MergeResult<RawText> result = null;
boolean hasSymlink = FileMode.SYMLINK.equals(modeO)
|| FileMode.SYMLINK.equals(modeT);
+
+ String currentPath = tw.getPathString();
+ // if the path is not a symlink in ours and theirs
if (!hasSymlink) {
try {
result = contentMerge(base, ours, theirs, attributes,
getContentMergeStrategy());
- } catch (BinaryBlobException e) {
- // result == null
- }
- }
- if (result == null) {
- switch (getContentMergeStrategy()) {
- case OURS:
- keep(ourDce);
- return true;
- case THEIRS:
- DirCacheEntry e = add(tw.getRawPath(), theirs,
- DirCacheEntry.STAGE_0, EPOCH, 0);
- if (e != null) {
- addToCheckout(tw.getPathString(), e, attributes);
+ if (result.containsConflicts() && !ignoreConflicts) {
+ result.setContainsConflicts(true);
+ unmergedPaths.add(currentPath);
+ } else if (ignoreConflicts) {
+ result.setContainsConflicts(false);
}
+ updateIndex(base, ours, theirs, result, attributes[T_OURS]);
+ workTreeUpdater.markAsModified(currentPath);
+ // Entry is null - only add the metadata
+ addToCheckout(currentPath, null, attributes);
return true;
- default:
- result = new MergeResult<>(Collections.emptyList());
- result.setContainsConflicts(true);
- break;
+ } catch (BinaryBlobException e) {
+ // The file is binary in either OURS, THEIRS or BASE
+ if (ignoreConflicts) {
+ // When ignoreConflicts = true, we simply keep OURS to allow virtual commit to be built.
+ keep(ourDce);
+ return true;
+ }
}
}
- if (ignoreConflicts) {
- result.setContainsConflicts(false);
+ switch (getContentMergeStrategy()) {
+ case OURS:
+ keep(ourDce);
+ return true;
+ case THEIRS:
+ DirCacheEntry e = add(tw.getRawPath(), theirs,
+ DirCacheEntry.STAGE_0, EPOCH, 0);
+ if (e != null) {
+ addToCheckout(currentPath, e, attributes);
+ }
+ return true;
+ default:
+ result = new MergeResult<>(Collections.emptyList());
+ result.setContainsConflicts(true);
+ break;
}
- String currentPath = tw.getPathString();
if (hasSymlink) {
if (ignoreConflicts) {
+ result.setContainsConflicts(false);
if (((modeT & FileMode.TYPE_MASK) == FileMode.TYPE_FILE)) {
DirCacheEntry e = add(tw.getRawPath(), theirs,
DirCacheEntry.STAGE_0, EPOCH, 0);
@@ -1312,9 +1362,9 @@ public class ResolveMerger extends ThreeWayMerger {
keep(ourDce);
}
} else {
- // Record the conflict
DirCacheEntry e = addConflict(base, ours, theirs);
mergeResults.put(currentPath, result);
+ unmergedPaths.add(currentPath);
// If theirs is a file, check it out. In link/file
// conflicts, C git prefers the file.
if (((modeT & FileMode.TYPE_MASK) == FileMode.TYPE_FILE)
@@ -1323,14 +1373,14 @@ public class ResolveMerger extends ThreeWayMerger {
}
}
} else {
- updateIndex(base, ours, theirs, result, attributes[T_OURS]);
- }
- if (result.containsConflicts() && !ignoreConflicts) {
+ // This is reachable if contentMerge() call above threw BinaryBlobException, so we don't
+ // need to check ignoreConflicts here, since it's already handled above.
+ result.setContainsConflicts(true);
+ addConflict(base, ours, theirs);
unmergedPaths.add(currentPath);
+ mergeResults.put(currentPath, result);
}
- workTreeUpdater.markAsModified(currentPath);
- // Entry is null - only adds the metadata.
- addToCheckout(currentPath, null, attributes);
+ return true;
} else if (modeO != modeT) {
// OURS or THEIRS has been deleted
if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw
@@ -1432,15 +1482,21 @@ public class ResolveMerger extends ThreeWayMerger {
* specified as <code>null</code> then an empty text will be used instead.
*
* @param base
+ * used to parse base tree
* @param ours
+ * used to parse ours tree
* @param theirs
+ * used to parse theirs tree
* @param attributes
+ * attributes for the different stages
* @param strategy
+ * merge strategy
*
* @return the result of the content merge
* @throws BinaryBlobException
* if any of the blobs looks like a binary blob
* @throws IOException
+ * if an IO error occurred
*/
private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
CanonicalTreeParser ours, CanonicalTreeParser theirs,
@@ -1454,11 +1510,26 @@ public class ResolveMerger extends ThreeWayMerger {
: getRawText(ours.getEntryObjectId(), attributes[T_OURS]);
RawText theirsText = theirs == null ? RawText.EMPTY_TEXT
: getRawText(theirs.getEntryObjectId(), attributes[T_THEIRS]);
- mergeAlgorithm.setContentMergeStrategy(strategy);
+ mergeAlgorithm.setContentMergeStrategy(
+ getAttributesContentMergeStrategy(attributes[T_OURS],
+ strategy));
return mergeAlgorithm.merge(RawTextComparator.DEFAULT, baseText,
ourText, theirsText);
}
+ private ContentMergeStrategy getAttributesContentMergeStrategy(
+ Attributes attributes, ContentMergeStrategy strategy) {
+ Attribute attr = attributes.get(Constants.ATTR_MERGE);
+ if (attr != null) {
+ String attrValue = attr.getValue();
+ if (attrValue != null && attrValue
+ .equals(Constants.ATTR_BUILTIN_UNION_MERGE_DRIVER)) {
+ return ContentMergeStrategy.UNION;
+ }
+ }
+ return strategy;
+ }
+
private boolean isIndexDirty() {
if (inCore) {
return false;
@@ -1516,11 +1587,17 @@ public class ResolveMerger extends ThreeWayMerger {
* correct stages to the index.
*
* @param base
+ * used to parse base tree
* @param ours
+ * used to parse ours tree
* @param theirs
+ * used to parse theirs tree
* @param result
+ * merge result
* @param attributes
+ * the file's attributes
* @throws IOException
+ * if an IO error occurred
*/
private void updateIndex(CanonicalTreeParser base,
CanonicalTreeParser ours, CanonicalTreeParser theirs,
@@ -1571,20 +1648,17 @@ public class ResolveMerger extends ThreeWayMerger {
* the files .gitattributes entries
* @return the working tree file to which the merged content was written.
* @throws IOException
+ * if an IO error occurred
*/
private File writeMergedFile(TemporaryBuffer rawMerged,
Attributes attributes)
throws IOException {
File workTree = nonNullRepo().getWorkTree();
- FS fs = nonNullRepo().getFS();
- File of = new File(workTree, tw.getPathString());
- File parentFolder = of.getParentFile();
+ String gitPath = tw.getPathString();
+ File of = new File(workTree, gitPath);
EolStreamType eol = workTreeUpdater.detectCheckoutStreamType(attributes);
- if (!fs.exists(parentFolder)) {
- parentFolder.mkdirs();
- }
workTreeUpdater.updateFileWithContent(rawMerged::openInputStream,
- eol, tw.getSmudgeCommand(attributes), of.getPath(), of);
+ eol, tw.getSmudgeCommand(attributes), gitPath, of);
return of;
}
@@ -1659,7 +1733,6 @@ public class ResolveMerger extends ThreeWayMerger {
return FileMode.GITLINK.equals(mode);
}
- /** {@inheritDoc} */
@Override
public ObjectId getResultTreeId() {
return (resultTree == null) ? null : resultTree.toObjectId();
@@ -1787,6 +1860,18 @@ public class ResolveMerger extends ThreeWayMerger {
this.workingTreeIterator = workingTreeIterator;
}
+ /**
+ * Sets the {@link AttributesNodeProvider} to be used by this merger.
+ *
+ * @param attributesNodeProvider
+ * the attributeNodeProvider to set
+ * @since 6.10.1
+ */
+ public void setAttributesNodeProvider(
+ AttributesNodeProvider attributesNodeProvider) {
+ this.attributesNodeProvider = attributesNodeProvider;
+ }
+
/**
* The resolve conflict way of three way merging
@@ -1819,6 +1904,7 @@ public class ResolveMerger extends ThreeWayMerger {
* content-merge conflicts.
* @return whether the trees merged cleanly
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.5
*/
protected boolean mergeTrees(AbstractTreeIterator baseTree,
@@ -1830,6 +1916,9 @@ public class ResolveMerger extends ThreeWayMerger {
WorkTreeUpdater.createWorkTreeUpdater(db, dircache);
dircache = workTreeUpdater.getLockedDirCache();
tw = new NameConflictTreeWalk(db, reader);
+ if (attributesNodeProvider != null) {
+ tw.setAttributesNodeProvider(attributesNodeProvider);
+ }
tw.addTree(baseTree);
tw.setHead(tw.addTree(headTree));
@@ -1878,6 +1967,7 @@ public class ResolveMerger extends ThreeWayMerger {
* {@link org.eclipse.jgit.merge.ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)}
* @return Whether the trees merged cleanly.
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.5
*/
protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java
index 7055ccd127..6064ebe326 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java
@@ -43,25 +43,21 @@ public class StrategyOneSided extends MergeStrategy {
treeIndex = index;
}
- /** {@inheritDoc} */
@Override
public String getName() {
return strategyName;
}
- /** {@inheritDoc} */
@Override
public Merger newMerger(Repository db) {
return new OneSide(db, treeIndex);
}
- /** {@inheritDoc} */
@Override
public Merger newMerger(Repository db, boolean inCore) {
return new OneSide(db, treeIndex);
}
- /** {@inheritDoc} */
@Override
public Merger newMerger(ObjectInserter inserter, Config config) {
return new OneSide(inserter, treeIndex);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java
index c9512edf63..a74bfc0379 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyRecursive.java
@@ -21,25 +21,21 @@ import org.eclipse.jgit.lib.Repository;
*/
public class StrategyRecursive extends StrategyResolve {
- /** {@inheritDoc} */
@Override
public ThreeWayMerger newMerger(Repository db) {
return new RecursiveMerger(db, false);
}
- /** {@inheritDoc} */
@Override
public ThreeWayMerger newMerger(Repository db, boolean inCore) {
return new RecursiveMerger(db, inCore);
}
- /** {@inheritDoc} */
@Override
public ThreeWayMerger newMerger(ObjectInserter inserter, Config config) {
return new RecursiveMerger(inserter, config);
}
- /** {@inheritDoc} */
@Override
public String getName() {
return "recursive"; //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java
index 5991ef68a3..a686fd0964 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyResolve.java
@@ -19,25 +19,21 @@ import org.eclipse.jgit.lib.Repository;
*/
public class StrategyResolve extends ThreeWayMergeStrategy {
- /** {@inheritDoc} */
@Override
public ThreeWayMerger newMerger(Repository db) {
return new ResolveMerger(db, false);
}
- /** {@inheritDoc} */
@Override
public ThreeWayMerger newMerger(Repository db, boolean inCore) {
return new ResolveMerger(db, inCore);
}
- /** {@inheritDoc} */
@Override
public ThreeWayMerger newMerger(ObjectInserter inserter, Config config) {
return new ResolveMerger(inserter, config);
}
- /** {@inheritDoc} */
@Override
public String getName() {
return "resolve"; //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java
index ff40fc1d68..c3180abd35 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategySimpleTwoWayInCore.java
@@ -43,26 +43,22 @@ public class StrategySimpleTwoWayInCore extends ThreeWayMergeStrategy {
//
}
- /** {@inheritDoc} */
@Override
public String getName() {
return "simple-two-way-in-core"; //$NON-NLS-1$
}
- /** {@inheritDoc} */
@Override
public ThreeWayMerger newMerger(Repository db) {
return new InCoreMerger(db);
}
- /** {@inheritDoc} */
@Override
public ThreeWayMerger newMerger(Repository db, boolean inCore) {
// This class is always inCore, so ignore the parameter
return newMerger(db);
}
- /** {@inheritDoc} */
@Override
public ThreeWayMerger newMerger(ObjectInserter inserter, Config config) {
return new InCoreMerger(inserter);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java
index 411789fd95..8cefa6566e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMergeStrategy.java
@@ -16,11 +16,9 @@ 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 {
- /** {@inheritDoc} */
@Override
public abstract ThreeWayMerger newMerger(Repository db);
- /** {@inheritDoc} */
@Override
public abstract ThreeWayMerger newMerger(Repository db, boolean inCore);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java
index f283edee01..68a1b5e509 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java
@@ -88,7 +88,6 @@ public abstract class ThreeWayMerger extends Merger {
}
}
- /** {@inheritDoc} */
@Override
public boolean merge(AnyObjectId... tips) throws IOException {
if (tips.length != 2)
@@ -96,7 +95,6 @@ public abstract class ThreeWayMerger extends Merger {
return super.merge(tips);
}
- /** {@inheritDoc} */
@Override
public ObjectId getBaseCommitId() {
return baseCommitId;
@@ -108,6 +106,7 @@ public abstract class ThreeWayMerger extends Merger {
* @return an iterator over the caller-specified merge base, or the natural
* merge base of the two input commits.
* @throws java.io.IOException
+ * if an IO error occurred
*/
protected AbstractTreeIterator mergeBase() throws IOException {
if (baseTree != null) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java b/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java
index 881873de6f..16d17637e2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java
@@ -83,6 +83,8 @@ public class NLS {
* injected as described in the
* {@link org.eclipse.jgit.nls.TranslationBundle}.
*
+ * @param <T>
+ * type of the translation bundle
* @param type
* required bundle type
* @return an instance of the required bundle type
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/DefaultNoteMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/DefaultNoteMerger.java
index bde0b9e445..b65f28c918 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/DefaultNoteMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/DefaultNoteMerger.java
@@ -34,7 +34,6 @@ import org.eclipse.jgit.util.io.UnionInputStream;
*/
public class DefaultNoteMerger implements NoteMerger {
- /** {@inheritDoc} */
@Override
public Note merge(Note base, Note ours, Note theirs, ObjectReader reader,
ObjectInserter inserter) throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/Note.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/Note.java
index c35696323e..2f719bf453 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/Note.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/Note.java
@@ -45,7 +45,6 @@ public class Note extends ObjectId {
data = newData;
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java
index 93adf36a3a..ed45f81476 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java
@@ -166,7 +166,6 @@ public class NoteMap implements Iterable<Note> {
this.reader = reader;
}
- /** {@inheritDoc} */
@Override
public Iterator<Note> iterator() {
try {
@@ -349,7 +348,11 @@ public class NoteMap implements Iterable<Note> {
return root.writeTree(inserter);
}
- /** @return the root note bucket */
+ /**
+ * Get the root note bucket
+ *
+ * @return the root note bucket
+ */
InMemoryNoteBucket getRoot() {
return root;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java
index a6ea523a78..30512c17b8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java
@@ -94,6 +94,7 @@ public class NoteMapMerger {
* theirs version of the note tree
* @return merge result as a new NoteMap
* @throws java.io.IOException
+ * if an IO error occurred
*/
public NoteMap merge(NoteMap base, NoteMap ours, NoteMap theirs)
throws IOException {
@@ -113,11 +114,16 @@ public class NoteMapMerger {
* between base, ours and theirs.
*
* @param treeDepth
+ * depth of the tree
* @param base
+ * base version
* @param ours
+ * ours version
* @param theirs
+ * theirs version
* @return merge result as an InMemoryBucket
* @throws IOException
+ * if an IO error occurred
*/
private InMemoryNoteBucket merge(int treeDepth, InMemoryNoteBucket base,
InMemoryNoteBucket ours, InMemoryNoteBucket theirs)
@@ -193,7 +199,7 @@ public class NoteMapMerger {
if (child == null)
return;
if (child instanceof InMemoryNoteBucket)
- b.setBucket(cell, ((InMemoryNoteBucket) child).writeTree(inserter));
+ b.setBucket(cell, child.writeTree(inserter));
else
b.setBucket(cell, child.getTreeId());
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java
index 4ba7cca51e..e29af614a6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedFileHeader.java
@@ -40,7 +40,6 @@ public class CombinedFileHeader extends FileHeader {
super(b, offset);
}
- /** {@inheritDoc} */
@Override
@SuppressWarnings("unchecked")
public List<? extends CombinedHunkHeader> getHunks() {
@@ -48,9 +47,6 @@ public class CombinedFileHeader extends FileHeader {
}
/**
- * {@inheritDoc}
- * <p>
- *
* @return number of ancestor revisions mentioned in this diff.
*/
@Override
@@ -60,7 +56,7 @@ public class CombinedFileHeader extends FileHeader {
/**
* {@inheritDoc}
- * <p>
+ *
* @return get the file mode of the first parent.
*/
@Override
@@ -81,7 +77,6 @@ public class CombinedFileHeader extends FileHeader {
/**
* {@inheritDoc}
- * <p>
*
* @return get the object id of the first parent.
*/
@@ -101,7 +96,6 @@ public class CombinedFileHeader extends FileHeader {
return oldIds[nthParent];
}
- /** {@inheritDoc} */
@Override
public String getScriptText(Charset ocs, Charset ncs) {
final Charset[] cs = new Charset[getParentCount() + 1];
@@ -110,11 +104,6 @@ public class CombinedFileHeader extends FileHeader {
return getScriptText(cs);
}
- /**
- * {@inheritDoc}
- * <p>
- * Convert the patch script for this file into a string.
- */
@Override
public String getScriptText(Charset[] charsetGuess) {
return super.getScriptText(charsetGuess);
@@ -156,7 +145,6 @@ public class CombinedFileHeader extends FileHeader {
return ptr;
}
- /** {@inheritDoc} */
@Override
protected void parseIndexLine(int ptr, int eol) {
// "index $asha1,$bsha1..$csha1"
@@ -178,7 +166,6 @@ public class CombinedFileHeader extends FileHeader {
oldModes = new FileMode[oldIds.length];
}
- /** {@inheritDoc} */
@Override
protected void parseNewFileMode(int ptr, int eol) {
for (int i = 0; i < oldModes.length; i++)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java
index 263b1b9ddc..49cf499865 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java
@@ -45,13 +45,11 @@ public class CombinedHunkHeader extends HunkHeader {
}
}
- /** {@inheritDoc} */
@Override
public CombinedFileHeader getFileHeader() {
return (CombinedFileHeader) super.getFileHeader();
}
- /** {@inheritDoc} */
@Override
public OldImage getOldImage() {
return getOldImage(0);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java
index 1e6fb780b2..a47b73dc34 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java
@@ -187,6 +187,13 @@ public class FileHeader extends DiffEntry {
return getScriptText(new Charset[] { oldCharset, newCharset });
}
+ /**
+ * Convert the patch script for this file into a string.
+ *
+ * @param charsetGuess
+ * hint which charset is used
+ * @return the patch script, as a Unicode string.
+ */
String getScriptText(Charset[] charsetGuess) {
if (getHunks().isEmpty()) {
// If we have no hunks then we can safely assume the entire
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java
index 5618a71782..8d21b6dabb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java
@@ -91,7 +91,6 @@ public class FormatError {
return RawParseUtils.decode(UTF_8, buf, offset, eol);
}
- /** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder r = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/HunkHeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/HunkHeader.java
index 4b59fcfc63..9e98f9f272 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/HunkHeader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/HunkHeader.java
@@ -42,27 +42,47 @@ public class HunkHeader {
/** 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. */
+ /**
+ * Get line number where hunk starts
+ *
+ * @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. */
+ /**
+ * Get number of lines this hunk covers
+ *
+ * @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. */
+ /**
+ * Get number of lines deleted by the post-image
+ *
+ * @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. */
+ /**
+ * Get number of lines added by the post-image
+ *
+ * @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. */
+ /**
+ * Get id of the pre-image file
+ *
+ * @return object id of the pre-image file.
+ */
public abstract AbbreviatedObjectId getId();
}
@@ -409,7 +429,6 @@ public class HunkHeader {
offsets[fileIdx] = end < 0 ? s.length() : end + 1;
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/PatchApplier.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/PatchApplier.java
index da698d6bf6..23e09b9479 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/PatchApplier.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/PatchApplier.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022, Google Inc. and others
+ * Copyright (C) 2023, Google Inc. and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -23,6 +23,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
@@ -33,12 +34,13 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.zip.InflaterInputStream;
+
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.FilterFailedException;
-import org.eclipse.jgit.api.errors.PatchFormatException;
import org.eclipse.jgit.attributes.Attribute;
import org.eclipse.jgit.attributes.Attributes;
import org.eclipse.jgit.attributes.FilterCommand;
@@ -52,6 +54,7 @@ import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
import org.eclipse.jgit.dircache.DirCacheCheckout.StreamSupplier;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheIterator;
+import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IndexWriteException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Config;
@@ -59,6 +62,7 @@ import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.FileModeCache;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectLoader;
@@ -81,10 +85,12 @@ import org.eclipse.jgit.util.LfsFactory;
import org.eclipse.jgit.util.LfsFactory.LfsInputStream;
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.StringUtils;
+import org.eclipse.jgit.util.SystemReader;
import org.eclipse.jgit.util.TemporaryBuffer;
import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;
import org.eclipse.jgit.util.io.BinaryDeltaInputStream;
import org.eclipse.jgit.util.io.BinaryHunkInputStream;
+import org.eclipse.jgit.util.io.CountingOutputStream;
import org.eclipse.jgit.util.io.EolStreamTypeUtil;
import org.eclipse.jgit.util.sha1.SHA1;
@@ -97,11 +103,12 @@ import org.eclipse.jgit.util.sha1.SHA1;
* @since 6.4
*/
public class PatchApplier {
-
private static final byte[] NO_EOL = "\\ No newline at end of file" //$NON-NLS-1$
.getBytes(StandardCharsets.US_ASCII);
- /** The tree before applying the patch. Only non-null for inCore operation. */
+ /**
+ * The tree before applying the patch. Only non-null for inCore operation.
+ */
@Nullable
private final RevTree beforeTree;
@@ -111,10 +118,14 @@ public class PatchApplier {
private final ObjectReader reader;
+ private final Charset charset;
+
private WorkingTreeOptions workingTreeOptions;
private int inCoreSizeLimit;
+ private boolean allowConflicts;
+
/**
* @param repo
* repository to apply the patch in
@@ -124,7 +135,8 @@ public class PatchApplier {
inserter = repo.newObjectInserter();
reader = inserter.newReader();
beforeTree = null;
-
+ allowConflicts = false;
+ charset = StandardCharsets.UTF_8;
Config config = repo.getConfig();
workingTreeOptions = config.get(WorkingTreeOptions.KEY);
inCoreSizeLimit = config.getInt(ConfigConstants.CONFIG_MERGE_SECTION,
@@ -139,11 +151,14 @@ public class PatchApplier {
* @param oi
* to be used for modifying objects
*/
- public PatchApplier(Repository repo, RevTree beforeTree, ObjectInserter oi) {
+ public PatchApplier(Repository repo, RevTree beforeTree,
+ ObjectInserter oi) {
this.repo = repo;
this.beforeTree = beforeTree;
inserter = oi;
reader = oi.newReader();
+ allowConflicts = false;
+ charset = StandardCharsets.UTF_8;
}
/**
@@ -153,35 +168,76 @@ public class PatchApplier {
* @since 6.3
*/
public static class Result {
-
/**
* A wrapper for a patch applying error that affects a given file.
*
* @since 6.6
*/
+ // TODO(ms): rename this class in next major release
+ @SuppressWarnings("JavaLangClash")
public static class Error {
+ final String msg;
- private String msg;
- private String oldFileName;
- private @Nullable HunkHeader hh;
+ final String oldFileName;
- private Error(String msg, String oldFileName,
- @Nullable HunkHeader hh) {
+ @Nullable
+ final HunkHeader hh;
+
+ final boolean isGitConflict;
+
+ Error(String msg, String oldFileName, @Nullable HunkHeader hh,
+ boolean isGitConflict) {
this.msg = msg;
this.oldFileName = oldFileName;
this.hh = hh;
+ this.isGitConflict = isGitConflict;
+ }
+
+ /**
+ * Signals if as part of encountering this error, conflict markers
+ * were added to the file.
+ *
+ * @return {@code true} if conflict markers were added for this
+ * error.
+ *
+ * @since 6.10
+ */
+ public boolean isGitConflict() {
+ return isGitConflict;
}
@Override
public String toString() {
if (hh != null) {
- return MessageFormat.format(JGitText.get().patchApplyErrorWithHunk,
- oldFileName, hh, msg);
+ return MessageFormat.format(
+ JGitText.get().patchApplyErrorWithHunk, oldFileName,
+ hh, msg);
}
- return MessageFormat.format(JGitText.get().patchApplyErrorWithoutHunk,
- oldFileName, msg);
+ return MessageFormat.format(
+ JGitText.get().patchApplyErrorWithoutHunk, oldFileName,
+ msg);
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || !(o instanceof Error)) {
+ return false;
+ }
+ Error error = (Error) o;
+ return Objects.equals(msg, error.msg)
+ && Objects.equals(oldFileName, error.oldFileName)
+ && Objects.equals(hh, error.hh)
+ && isGitConflict == error.isGitConflict;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(msg, oldFileName, hh,
+ Boolean.valueOf(isGitConflict));
+ }
}
private ObjectId treeId;
@@ -191,6 +247,8 @@ public class PatchApplier {
private List<Error> errors = new ArrayList<>();
/**
+ * Get modified paths
+ *
* @return List of modified paths.
*/
public List<String> getPaths() {
@@ -198,6 +256,8 @@ public class PatchApplier {
}
/**
+ * Get tree ID
+ *
* @return The applied tree ID.
*/
public ObjectId getTreeId() {
@@ -205,6 +265,8 @@ public class PatchApplier {
}
/**
+ * Get errors
+ *
* @return Errors occurred while applying the patch.
*
* @since 6.6
@@ -213,35 +275,15 @@ public class PatchApplier {
return errors;
}
- private void addError(String msg,String oldFileName, @Nullable HunkHeader hh) {
- errors.add(new Error(msg, oldFileName, hh));
+ private void addError(String msg, String oldFileName,
+ @Nullable HunkHeader hh) {
+ errors.add(new Error(msg, oldFileName, hh, false));
}
- }
- /**
- * Applies the given patch
- *
- * @param patchInput
- * the patch to apply.
- * @return the result of the patch
- * @throws PatchFormatException
- * if the patch cannot be parsed
- * @throws IOException
- * if the patch read fails
- * @deprecated use {@link #applyPatch(Patch)} instead
- */
- @Deprecated
- public Result applyPatch(InputStream patchInput)
- throws PatchFormatException, IOException {
- Patch p = new Patch();
- try (InputStream inStream = patchInput) {
- p.parse(inStream);
-
- if (!p.getErrors().isEmpty()) {
- throw new PatchFormatException(p.getErrors());
- }
+ private void addErrorWithGitConflict(String msg, String oldFileName,
+ @Nullable HunkHeader hh) {
+ errors.add(new Error(msg, oldFileName, hh, true));
}
- return applyPatch(p);
}
/**
@@ -251,6 +293,7 @@ public class PatchApplier {
* the patch to apply.
* @return the result of the patch
* @throws IOException
+ * if an IO error occurred
* @since 6.6
*/
public Result applyPatch(Patch p) throws IOException {
@@ -258,6 +301,7 @@ public class PatchApplier {
DirCache dirCache = inCore() ? DirCache.read(reader, beforeTree)
: repo.lockDirCache();
+ FileModeCache directoryCache = new FileModeCache(repo);
DirCacheBuilder dirCacheBuilder = dirCache.builder();
Set<String> modifiedPaths = new HashSet<>();
for (FileHeader fh : p.getFiles()) {
@@ -270,7 +314,8 @@ public class PatchApplier {
switch (type) {
case ADD: {
if (dest != null) {
- FileUtils.mkdirs(dest.getParentFile(), true);
+ directoryCache.safeCreateParentDirectory(fh.getNewPath(),
+ dest.getParentFile(), false);
FileUtils.createNewFile(dest);
}
apply(fh.getNewPath(), dirCache, dirCacheBuilder, dest, fh, result);
@@ -295,7 +340,8 @@ public class PatchApplier {
* apply() will write a fresh stream anyway, which will
* overwrite if there were hunks in the patch.
*/
- FileUtils.mkdirs(dest.getParentFile(), true);
+ directoryCache.safeCreateParentDirectory(fh.getNewPath(),
+ dest.getParentFile(), false);
FileUtils.rename(src, dest,
StandardCopyOption.ATOMIC_MOVE);
}
@@ -306,7 +352,8 @@ public class PatchApplier {
}
case COPY: {
if (!inCore()) {
- FileUtils.mkdirs(dest.getParentFile(), true);
+ directoryCache.safeCreateParentDirectory(fh.getNewPath(),
+ dest.getParentFile(), false);
Files.copy(src.toPath(), dest.toPath());
}
apply(fh.getOldPath(), dirCache, dirCacheBuilder, dest, fh, result);
@@ -340,8 +387,19 @@ public class PatchApplier {
return result;
}
+ /**
+ * Sets up the {@link PatchApplier} to apply patches even if they conflict.
+ *
+ * @return the {@link PatchApplier} to apply any patches
+ * @since 6.10
+ */
+ public PatchApplier allowConflicts() {
+ allowConflicts = true;
+ return this;
+ }
+
private File getFile(String path) {
- return (inCore()) ? null : new File(repo.getWorkTree(), path);
+ return inCore() ? null : new File(repo.getWorkTree(), path);
}
/* returns null if the path is not found. */
@@ -401,9 +459,28 @@ public class PatchApplier {
fh.getPatchType()), fh.getNewPath(), null);
isValid = false;
}
+ if (srcShouldExist && !validGitPath(fh.getOldPath())) {
+ result.addError(JGitText.get().applyPatchSourceInvalid,
+ fh.getOldPath(), null);
+ isValid = false;
+ }
+ if (destShouldNotExist && !validGitPath(fh.getNewPath())) {
+ result.addError(JGitText.get().applyPatchDestInvalid,
+ fh.getNewPath(), null);
+ isValid = false;
+ }
return isValid;
}
+ private boolean validGitPath(String path) {
+ try {
+ SystemReader.getInstance().checkPath(path);
+ return true;
+ } catch (CorruptObjectException e) {
+ return false;
+ }
+ }
+
private static final int FILE_TREE_INDEX = 1;
/**
@@ -423,6 +500,7 @@ public class PatchApplier {
* @param result
* The patch application result.
* @throws IOException
+ * if an IO error occurred
*/
private void apply(String pathWithOriginalContent, DirCache dirCache,
DirCacheBuilder dirCacheBuilder, @Nullable File f, FileHeader fh, Result result)
@@ -503,7 +581,9 @@ public class PatchApplier {
convertCrLf);
resultStreamLoader = applyText(raw, fh, result);
}
- if (resultStreamLoader == null || !result.getErrors().isEmpty()) {
+ if (resultStreamLoader == null
+ || (!result.getErrors().isEmpty() && result.getErrors().stream()
+ .anyMatch(e -> !e.msg.equals("cannot apply hunk")))) { //$NON-NLS-1$
return;
}
@@ -777,7 +857,9 @@ public class PatchApplier {
* The patch application result
* @return a loader for the new content, or null if invalid.
* @throws IOException
+ * if an IO error occurred
* @throws UnsupportedOperationException
+ * if an operation isn't supported
*/
private @Nullable ContentStreamLoader applyBinary(String path, File f, FileHeader fh,
StreamSupplier inputSupplier, ObjectId id, Result result)
@@ -832,6 +914,7 @@ public class PatchApplier {
}
}
+ @SuppressWarnings("ByteBufferBackingArray")
private @Nullable ContentStreamLoader applyText(RawText rt, FileHeader fh, Result result)
throws IOException {
List<ByteBuffer> oldLines = new ArrayList<>(rt.size());
@@ -922,9 +1005,51 @@ public class PatchApplier {
}
}
if (!applies) {
- result.addError(JGitText.get().applyTextPatchCannotApplyHunk,
- fh.getOldPath(), hh);
- return null;
+ if (!allowConflicts) {
+ result.addError(
+ JGitText.get().applyTextPatchCannotApplyHunk,
+ fh.getOldPath(), hh);
+ return null;
+ }
+ // Insert conflict markers. This is best-guess because the
+ // file might have changed completely. But at least we give
+ // the user a graceful state that they can resolve manually.
+ // An alternative to this is using the 3-way merger. This
+ // only works if the pre-image SHA is contained in the repo.
+ // If that was the case, cherry-picking the original commit
+ // should be preferred to apply a patch.
+ result.addErrorWithGitConflict("cannot apply hunk", fh.getOldPath(), hh); //$NON-NLS-1$
+ newLines.add(Math.min(applyAt++, newLines.size()),
+ asBytes("<<<<<<< HEAD")); //$NON-NLS-1$
+ applyAt += hh.getOldImage().lineCount;
+ newLines.add(Math.min(applyAt++, newLines.size()),
+ asBytes("=======")); //$NON-NLS-1$
+
+ int sz = hunkLines.size();
+ for (int j = 1; j < sz; j++) {
+ ByteBuffer hunkLine = hunkLines.get(j);
+ if (!hunkLine.hasRemaining()) {
+ // Completely empty line; accept as empty context
+ // line
+ applyAt++;
+ lastWasRemoval = false;
+ continue;
+ }
+ switch (hunkLine.array()[hunkLine.position()]) {
+ case ' ':
+ case '+':
+ newLines.add(Math.min(applyAt++, newLines.size()),
+ slice(hunkLine, 1));
+ break;
+ case '-':
+ case '\\':
+ default:
+ break;
+ }
+ }
+ newLines.add(Math.min(applyAt++, newLines.size()),
+ asBytes(">>>>>>> PATCH")); //$NON-NLS-1$
+ continue;
}
// Hunk applies at applyAt. Apply it, and update afterLastHunk and
// lineNumberShift
@@ -971,11 +1096,18 @@ public class PatchApplier {
} else if (!rt.isMissingNewlineAtEnd()) {
newLines.add(null);
}
+ return toContentStreamLoader(newLines);
+ }
+ private static ContentStreamLoader toContentStreamLoader(
+ List<ByteBuffer> newLines) throws IOException {
// We could check if old == new, but the short-circuiting complicates
// logic for inCore patching, so just write the new thing regardless.
TemporaryBuffer buffer = new TemporaryBuffer.LocalFile(null);
- try (OutputStream out = buffer) {
+ // TemporaryBuffer::length reports incorrect length until the buffer
+ // is closed. To use it as input for ContentStreamLoader below, we
+ // need a wrapper with a reliable in-progress length.
+ try (CountingOutputStream out = new CountingOutputStream(buffer)) {
for (Iterator<ByteBuffer> l = newLines.iterator(); l.hasNext();) {
ByteBuffer line = l.next();
if (line == null) {
@@ -988,10 +1120,15 @@ public class PatchApplier {
}
}
return new ContentStreamLoader(buffer::openInputStream,
- buffer.length());
+ out.getCount());
}
}
+ private ByteBuffer asBytes(String str) {
+ return ByteBuffer.wrap(str.getBytes(charset));
+ }
+
+ @SuppressWarnings("ByteBufferBackingArray")
private boolean canApplyAt(List<ByteBuffer> hunkLines,
List<ByteBuffer> newLines, int line) {
int sz = hunkLines.size();
@@ -1023,11 +1160,13 @@ public class PatchApplier {
return true;
}
+ @SuppressWarnings("ByteBufferBackingArray")
private ByteBuffer slice(ByteBuffer b, int off) {
int newOffset = b.position() + off;
return ByteBuffer.wrap(b.array(), newOffset, b.limit() - newOffset);
}
+ @SuppressWarnings("ByteBufferBackingArray")
private boolean isNoNewlineAtEnd(ByteBuffer hunkLine) {
return Arrays.equals(NO_EOL, 0, NO_EOL.length, hunkLine.array(),
hunkLine.position(), hunkLine.limit());
@@ -1078,4 +1217,4 @@ public class PatchApplier {
in.close();
}
}
-}
+} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java
index c11fca13d8..fa109e8208 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommit.java
@@ -179,7 +179,6 @@ public class PlotCommit<L extends PlotLane> extends RevCommit {
return (L) lane;
}
- /** {@inheritDoc} */
@Override
public void reset() {
forkingOffLanes = NO_LANES;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java
index 458f240982..3177de17e8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java
@@ -49,7 +49,6 @@ public class PlotCommitList<L extends PlotLane> extends
private final HashMap<PlotLane, Integer> laneLength = new HashMap<>(
32);
- /** {@inheritDoc} */
@Override
public void clear() {
super.clear();
@@ -59,7 +58,6 @@ public class PlotCommitList<L extends PlotLane> extends
laneLength.clear();
}
- /** {@inheritDoc} */
@Override
public void source(RevWalk w) {
if (!(w instanceof PlotWalk))
@@ -91,7 +89,6 @@ public class PlotCommitList<L extends PlotLane> extends
result.add((L) p);
}
- /** {@inheritDoc} */
@SuppressWarnings("ReferenceEquality")
@Override
protected void enter(int index, PlotCommit<L> currCommit) {
@@ -184,6 +181,7 @@ public class PlotCommitList<L extends PlotLane> extends
* @param index
* the index of <code>currCommit</code> in the list
* @param currCommit
+ * the current commit
* @param childOnLane
* the direct child on the same lane as <code>currCommit</code>,
* may be null if <code>currCommit</code> is the first commit on
@@ -287,8 +285,11 @@ public class PlotCommitList<L extends PlotLane> extends
* All blockades on the lane must be resolved before calling this method.
*
* @param commitIndex
+ * commit index
* @param child
+ * child to connect
* @param laneToContinue
+ * lane to continue
*/
@SuppressWarnings("ReferenceEquality")
private void drawLaneToChild(final int commitIndex, PlotCommit child,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java
index 0582338652..c8c454a228 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java
@@ -49,7 +49,6 @@ public class PlotWalk extends RevWalk {
private Repository repository;
- /** {@inheritDoc} */
@Override
public void dispose() {
super.dispose();
@@ -83,6 +82,7 @@ public class PlotWalk extends RevWalk {
* @param refs
* additional refs
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void addAdditionalRefs(Iterable<Ref> refs) throws IOException {
for (Ref ref : refs) {
@@ -97,7 +97,6 @@ public class PlotWalk extends RevWalk {
}
}
- /** {@inheritDoc} */
@Override
public void sort(RevSort s, boolean use) {
if (s == RevSort.TOPO && !use)
@@ -105,13 +104,11 @@ public class PlotWalk extends RevWalk {
super.sort(s, use);
}
- /** {@inheritDoc} */
@Override
protected RevCommit createCommit(AnyObjectId id) {
return new PlotCommit(id);
}
- /** {@inheritDoc} */
@Override
public RevCommit next() throws MissingObjectException,
IncorrectObjectTypeException, IOException {
@@ -172,8 +169,9 @@ public class PlotWalk extends RevWalk {
}
long timeof(RevObject o) {
- if (o instanceof RevCommit)
- return ((RevCommit) o).getCommitTime();
+ if (o instanceof RevCommit) {
+ return ((RevCommit) o).getCommitTime() * 1000L;
+ }
if (o instanceof RevTag) {
RevTag tag = (RevTag) o;
try {
@@ -182,7 +180,7 @@ public class PlotWalk extends RevWalk {
return 0;
}
PersonIdent who = tag.getTaggerIdent();
- return who != null ? who.getWhen().getTime() : 0;
+ return who != null ? who.getWhenAsInstant().toEpochMilli() : 0;
}
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
index dda108bc69..73ae62a23f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/AbstractRevQueue.java
@@ -79,8 +79,6 @@ abstract class AbstractRevQueue extends Generator {
}
/**
- * {@inheritDoc}
- * <p>
* Remove the first commit from the queue.
*/
@Override
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmapWalker.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmapWalker.java
index 8cd5eb2238..7e65ffb8f6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmapWalker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmapWalker.java
@@ -50,12 +50,15 @@ public final class BitmapWalker {
/**
* Create a BitmapWalker.
*
- * @param walker walker to use when traversing the object graph.
- * @param bitmapIndex index to obtain bitmaps from.
- * @param pm progress monitor to report progress on.
+ * @param walker
+ * walker to use when traversing the object graph.
+ * @param bitmapIndex
+ * index to obtain bitmaps from.
+ * @param pm
+ * progress monitor to report progress on.
*/
- public BitmapWalker(
- ObjectWalk walker, BitmapIndex bitmapIndex, ProgressMonitor pm) {
+ public BitmapWalker(ObjectWalk walker, BitmapIndex bitmapIndex,
+ ProgressMonitor pm) {
this.walker = walker;
this.bitmapIndex = bitmapIndex;
this.pm = (pm == null) ? NullProgressMonitor.INSTANCE : pm;
@@ -178,8 +181,9 @@ public final class BitmapWalker {
for (ObjectId obj : start) {
Bitmap bitmap = bitmapIndex.getBitmap(obj);
- if (bitmap != null)
+ if (bitmap != null) {
bitmapResult.or(bitmap);
+ }
}
boolean marked = false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java
index cdd8073d6e..e855d8f700 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BlockRevQueue.java
@@ -45,8 +45,6 @@ abstract class BlockRevQueue extends AbstractRevQueue {
}
/**
- * {@inheritDoc}
- * <p>
* Reconfigure this queue to share the same free list as another.
* <p>
* Multiple revision queues can be connected to the same free list, making
@@ -56,6 +54,11 @@ abstract class BlockRevQueue extends AbstractRevQueue {
* <p>
* Free lists are not thread-safe. Applications must ensure that all queues
* sharing the same free list are doing so from only a single thread.
+ *
+ * @param q
+ * another FIFO queue that wants to share our queue's free list.
+ *
+ * @see Generator#shareFreeList
*/
@Override
public void shareFreeList(BlockRevQueue q) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevPriorityQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevPriorityQueue.java
new file mode 100644
index 0000000000..233dd64a3c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevPriorityQueue.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023, GerritForge Ltd
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.revwalk;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
+
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.PriorityQueue;
+
+/**
+ * A queue of commits sorted by commit time order using a Java PriorityQueue.
+ * For the commits with the same commit time insertion order will be preserved.
+ */
+class DateRevPriorityQueue extends DateRevQueue {
+ private PriorityQueue<RevCommitEntry> queue;
+
+ private final AtomicInteger sequence = new AtomicInteger(1);
+
+ /**
+ * Create an empty queue of commits sorted by commit time order.
+ */
+ public DateRevPriorityQueue() {
+ this(false);
+ }
+
+ /**
+ * Create an empty queue of commits sorted by commit time order.
+ *
+ * @param firstParent
+ * treat first element as a parent
+ */
+ DateRevPriorityQueue(boolean firstParent) {
+ super(firstParent);
+ initPriorityQueue();
+ }
+
+ private void initPriorityQueue() {
+ sequence.set(1);
+ queue = new PriorityQueue<>(Comparator.comparingInt(
+ (RevCommitEntry ent) -> ent.getEntry().getCommitTime())
+ .reversed()
+ .thenComparingInt(RevCommitEntry::getInsertSequenceNumber));
+ }
+
+ DateRevPriorityQueue(Generator s) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
+ this(s.firstParent);
+ for (;;) {
+ final RevCommit c = s.next();
+ if (c == null) {
+ break;
+ }
+ add(c);
+ }
+ }
+
+ @Override
+ public void add(RevCommit c) {
+ // PriorityQueue does not accept null values. To keep the same behaviour
+ // do the same check and throw the same exception before creating entry
+ if (c == null) {
+ throw new NullPointerException(JGitText.get().nullRevCommit);
+ }
+ queue.add(new RevCommitEntry(sequence.getAndIncrement(), c));
+ }
+
+ @Override
+ public RevCommit next() {
+ RevCommitEntry entry = queue.poll();
+ return entry == null ? null : entry.getEntry();
+ }
+
+ /**
+ * Peek at the next commit, without removing it.
+ *
+ * @return the next available commit; null if there are no commits left.
+ */
+ @Override
+ public @Nullable RevCommit peek() {
+ RevCommitEntry entry = queue.peek();
+ return entry == null ? null : entry.getEntry();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void clear() {
+ sequence.set(1);
+ queue.clear();
+ }
+
+ @Override
+ boolean everbodyHasFlag(int f) {
+ return queue.stream().map(RevCommitEntry::getEntry)
+ .noneMatch(c -> (c.flags & f) == 0);
+ }
+
+ @Override
+ boolean anybodyHasFlag(int f) {
+ return queue.stream().map(RevCommitEntry::getEntry)
+ .anyMatch(c -> (c.flags & f) != 0);
+ }
+
+ @Override
+ int outputType() {
+ return outputType | SORT_COMMIT_TIME_DESC;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder s = new StringBuilder();
+ for (RevCommitEntry e : queue) {
+ describe(s, e.getEntry());
+ }
+ return s.toString();
+ }
+
+ private static class RevCommitEntry {
+ private final int insertSequenceNumber;
+
+ private final RevCommit entry;
+
+ public RevCommitEntry(int insertSequenceNumber, RevCommit entry) {
+ this.insertSequenceNumber = insertSequenceNumber;
+ this.entry = entry;
+ }
+
+ public int getInsertSequenceNumber() {
+ return insertSequenceNumber;
+ }
+
+ public RevCommit getEntry() {
+ return entry;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java
index 0cabf07057..905dcb62ad 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DateRevQueue.java
@@ -8,11 +8,11 @@
*
* SPDX-License-Identifier: BSD-3-Clause
*/
-
package org.eclipse.jgit.revwalk;
import java.io.IOException;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -36,11 +36,17 @@ public class DateRevQueue extends AbstractRevQueue {
private int last = -1;
- /** Create an empty date queue. */
+ /** Create an empty DateRevQueue. */
public DateRevQueue() {
super(false);
}
+ /**
+ * Create an empty DateRevQueue.
+ *
+ * @param firstParent
+ * treat first element as a parent
+ */
DateRevQueue(boolean firstParent) {
super(firstParent);
}
@@ -56,7 +62,6 @@ public class DateRevQueue extends AbstractRevQueue {
}
}
- /** {@inheritDoc} */
@Override
public void add(RevCommit c) {
sinceLastIndex++;
@@ -102,7 +107,6 @@ public class DateRevQueue extends AbstractRevQueue {
}
}
- /** {@inheritDoc} */
@Override
public RevCommit next() {
final Entry q = head;
@@ -135,11 +139,10 @@ public class DateRevQueue extends AbstractRevQueue {
*
* @return the next available commit; null if there are no commits left.
*/
- public RevCommit peek() {
+ public @Nullable RevCommit peek() {
return head != null ? head.commit : null;
}
- /** {@inheritDoc} */
@Override
public void clear() {
head = null;
@@ -173,7 +176,6 @@ public class DateRevQueue extends AbstractRevQueue {
return outputType | SORT_COMMIT_TIME_DESC;
}
- /** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder s = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java
index ec0824cb0b..664f8fa3a7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthGenerator.java
@@ -55,10 +55,15 @@ class DepthGenerator extends Generator {
/**
* @param w
- * @param s Parent generator
+ * walk used for depth filtering
+ * @param s
+ * Parent generator
* @throws MissingObjectException
+ * if an object is missing
* @throws IncorrectObjectTypeException
+ * if an object has an unexpected type
* @throws IOException
+ * if an IO error occurred
*/
DepthGenerator(DepthWalk w, Generator s) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java
index 5277563ac4..a7ffd34233 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/DepthWalk.java
@@ -35,6 +35,8 @@ public interface DepthWalk {
int getDepth();
/**
+ * Get deepen-since value
+ *
* @return the deepen-since value; if not 0, this walk only returns commits
* whose commit time is at or after this limit
* @since 5.2
@@ -44,6 +46,8 @@ public interface DepthWalk {
}
/**
+ * Get deepen-not values
+ *
* @return the objects specified by the client using --shallow-exclude
* @since 5.2
*/
@@ -51,7 +55,11 @@ public interface DepthWalk {
return Collections.emptyList();
}
- /** @return flag marking commits that should become unshallow. */
+ /**
+ * Get unshallow flag
+ *
+ * @return flag marking commits that should become unshallow.
+ */
/**
* Get flag marking commits that should become unshallow.
*
@@ -67,7 +75,10 @@ public interface DepthWalk {
RevFlag getReinterestingFlag();
/**
- * @return flag marking commits that are to be excluded because of --shallow-exclude
+ * Get deepen-not flag
+ *
+ * @return flag marking commits that are to be excluded because of
+ * --shallow-exclude
* @since 5.2
*/
RevFlag getDeepenNotFlag();
@@ -85,12 +96,18 @@ public interface DepthWalk {
*/
boolean makesChildBoundary;
- /** @return depth of this commit, as found by the shortest path. */
+ /**
+ * Get depth
+ *
+ * @return depth of this commit, as found by the shortest path.
+ */
public int getDepth() {
return depth;
}
/**
+ * Whether at least one commit was excluded due to shallow fetch
+ *
* @return true if at least one of this commit's parents was excluded
* due to a shallow fetch setting, false otherwise
* @since 5.2
@@ -126,8 +143,12 @@ public interface DepthWalk {
private final RevFlag DEEPEN_NOT;
/**
- * @param repo Repository to walk
- * @param depth Maximum depth to return
+ * Create RevWalk
+ *
+ * @param repo
+ * Repository to walk
+ * @param depth
+ * Maximum depth to return
*/
public RevWalk(Repository repo, int depth) {
super(repo);
@@ -140,8 +161,12 @@ public interface DepthWalk {
}
/**
- * @param or ObjectReader to use
- * @param depth Maximum depth to return
+ * Create RevWalk
+ *
+ * @param or
+ * ObjectReader to use
+ * @param depth
+ * Maximum depth to return
*/
public RevWalk(ObjectReader or, int depth) {
super(or);
@@ -159,8 +184,11 @@ public interface DepthWalk {
* @param c
* Commit to mark
* @throws IOException
+ * if an IO error occurred
* @throws IncorrectObjectTypeException
+ * if object has an unexpected type
* @throws MissingObjectException
+ * if object is missing
*/
public void markRoot(RevCommit c) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
@@ -228,6 +256,8 @@ public interface DepthWalk {
}
/**
+ * Convert to ObjectWalk with same objects
+ *
* @since 4.5
*/
@Override
@@ -256,8 +286,12 @@ public interface DepthWalk {
private final RevFlag DEEPEN_NOT;
/**
- * @param repo Repository to walk
- * @param depth Maximum depth to return
+ * Create ObjectWalk
+ *
+ * @param repo
+ * Repository to walk
+ * @param depth
+ * Maximum depth to return
*/
public ObjectWalk(Repository repo, int depth) {
super(repo);
@@ -270,8 +304,12 @@ public interface DepthWalk {
}
/**
- * @param or Object Reader
- * @param depth Maximum depth to return
+ * Create ObjectWalk
+ *
+ * @param or
+ * Object Reader
+ * @param depth
+ * Maximum depth to return
*/
public ObjectWalk(ObjectReader or, int depth) {
super(or);
@@ -289,8 +327,11 @@ public interface DepthWalk {
* @param o
* Commit to mark
* @throws IOException
+ * if an IO error occurred
* @throws IncorrectObjectTypeException
+ * if object has an unexpected type
* @throws MissingObjectException
+ * if object is missing
*/
public void markRoot(RevObject o) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
@@ -313,8 +354,11 @@ public interface DepthWalk {
* @param c
* Commit to mark
* @throws MissingObjectException
+ * if object is missing
* @throws IncorrectObjectTypeException
+ * if object has an unexpected type
* @throws IOException
+ * if an IO error occurred
*/
public void markUnshallow(RevObject c) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java
index 0f8eddd6d4..996745c158 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FIFORevQueue.java
@@ -37,7 +37,6 @@ public class FIFORevQueue extends BlockRevQueue {
super(s);
}
- /** {@inheritDoc} */
@Override
public void add(RevCommit c) {
Block b = tail;
@@ -82,7 +81,6 @@ public class FIFORevQueue extends BlockRevQueue {
head = b;
}
- /** {@inheritDoc} */
@Override
public RevCommit next() {
final Block b = head;
@@ -99,7 +97,6 @@ public class FIFORevQueue extends BlockRevQueue {
return c;
}
- /** {@inheritDoc} */
@Override
public void clear() {
head = null;
@@ -135,7 +132,6 @@ public class FIFORevQueue extends BlockRevQueue {
}
}
- /** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder s = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FollowFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FollowFilter.java
index 3b54123f0b..12e6c4ea98 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FollowFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FollowFilter.java
@@ -11,12 +11,14 @@
package org.eclipse.jgit.revwalk;
import java.io.IOException;
+import java.util.Optional;
+import java.util.Set;
import org.eclipse.jgit.diff.DiffConfig;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.eclipse.jgit.treewalk.filter.ChangedPathTreeFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
/**
@@ -54,41 +56,49 @@ public class FollowFilter extends TreeFilter {
* @since 3.0
*/
public static FollowFilter create(String path, DiffConfig cfg) {
- return new FollowFilter(PathFilter.create(path), cfg);
+ return new FollowFilter(ChangedPathTreeFilter.create(path), cfg);
}
- private final PathFilter path;
+ private final ChangedPathTreeFilter path;
final DiffConfig cfg;
private RenameCallback renameCallback;
- FollowFilter(PathFilter path, DiffConfig cfg) {
+ FollowFilter(ChangedPathTreeFilter path, DiffConfig cfg) {
this.path = path;
this.cfg = cfg;
}
- /** @return the path this filter matches. */
/**
* Get the path this filter matches.
*
* @return the path this filter matches.
*/
public String getPath() {
- return path.getPath();
+ return path.getPaths().get(0);
}
- /** {@inheritDoc} */
@Override
public boolean include(TreeWalk walker)
throws MissingObjectException, IncorrectObjectTypeException,
IOException {
- return path.include(walker) && ANY_DIFF.include(walker);
+ return path.include(walker);
+ }
+
+ @Override
+ public boolean shouldTreeWalk(RevCommit c, RevWalk rw,
+ MutableBoolean cpfUsed) {
+ return path.shouldTreeWalk(c, rw, cpfUsed);
}
- /** {@inheritDoc} */
@Override
public boolean shouldBeRecursive() {
- return path.shouldBeRecursive() || ANY_DIFF.shouldBeRecursive();
+ return path.shouldBeRecursive();
+ }
+
+ @Override
+ public Optional<Set<byte[]>> getPathsBestEffort() {
+ return path.getPathsBestEffort();
}
/** {@inheritDoc} */
@@ -97,13 +107,10 @@ public class FollowFilter extends TreeFilter {
return new FollowFilter(path.clone(), cfg);
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
- return "(FOLLOW(" + path.toString() + ")" //
- + " AND " //
- + ANY_DIFF.toString() + ")";
+ return "(FOLLOW(" + path.toString() + "))";
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java
index 74e4c1edb7..af50794014 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterKey.java
@@ -51,7 +51,6 @@ public final class FooterKey {
return name;
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java
index 3d128a6bd3..fa7fb316c9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/FooterLine.java
@@ -11,6 +11,9 @@
package org.eclipse.jgit.revwalk;
import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import org.eclipse.jgit.util.RawParseUtils;
@@ -47,6 +50,137 @@ public final class FooterLine {
}
/**
+ * Extract the footer lines from the given message.
+ *
+ * @param str
+ * the message to extract footers from.
+ * @return ordered list of footer lines; empty list if no footers found.
+ * @see RevCommit#getFooterLines()
+ * @since 6.7
+ */
+ public static List<FooterLine> fromMessage(
+ String str) {
+ return fromMessage(str.getBytes());
+ }
+
+ /**
+ * Extract the footer lines from the given message.
+ *
+ * @param raw
+ * the raw message to extract footers from.
+ * @return ordered list of footer lines; empty list if no footers found.
+ * @see RevCommit#getFooterLines()
+ * @since 6.7
+ */
+ public static List<FooterLine> fromMessage(
+ byte[] raw) {
+ // Find the end of the last paragraph.
+ int parEnd = raw.length;
+ for (; parEnd > 0 && (raw[parEnd - 1] == '\n'
+ || raw[parEnd - 1] == ' '); --parEnd) {
+ // empty
+ }
+
+ // The first non-header line is never a footer.
+ int msgB = RawParseUtils.nextLfSkippingSplitLines(raw,
+ RawParseUtils.hasAnyKnownHeaders(raw)
+ ? RawParseUtils.commitMessage(raw, 0)
+ : 0);
+ ArrayList<FooterLine> r = new ArrayList<>(4);
+ Charset enc = RawParseUtils.guessEncoding(raw);
+
+ // Search for the beginning of last paragraph
+ int parStart = parEnd;
+ for (; parStart > msgB; --parStart) {
+ if (parStart < 2) {
+ // Too close to beginning: this is not a raw message
+ parStart = 0;
+ break;
+ }
+ if (raw[parStart - 1] == '\n' && raw[parStart - 2] == '\n') {
+ break;
+ }
+ }
+
+ for (int ptr = parStart; ptr < parEnd;) {
+ int keyStart = ptr;
+ int keyEnd = RawParseUtils.endOfFooterLineKey(raw, ptr);
+ if (keyEnd < 0) {
+ // Not a well-formed footer line, skip it.
+ ptr = RawParseUtils.nextLF(raw, ptr);
+ continue;
+ }
+
+ // Skip over the ': *' at the end of the key before the value.
+ int valStart;
+ int valEnd;
+ for (valStart = keyEnd + 1; valStart < raw.length
+ && raw[valStart] == ' '; ++valStart) {
+ // empty
+ }
+
+ for(ptr = valStart;;) {
+ ptr = RawParseUtils.nextLF(raw, ptr);
+ // Next line starts with whitespace for a multiline footer.
+ if (ptr == raw.length || raw[ptr] != ' ') {
+ valEnd = raw[ptr - 1] == '\n' ? ptr - 1 : ptr;
+ break;
+ }
+ }
+ if (keyStart == msgB) {
+ // Fist line cannot be a footer
+ continue;
+ }
+ r.add(new FooterLine(raw, enc, keyStart, keyEnd, valStart, valEnd));
+ }
+
+ return r;
+ }
+
+ /**
+ * Get the values of all footer lines with the given key.
+ *
+ * @param footers
+ * list of footers to find the values in.
+ * @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 #fromMessage
+ * @since 6.7
+ */
+ public static List<String> getValues(List<FooterLine> footers, String keyName) {
+ return getValues(footers, new FooterKey(keyName));
+ }
+
+ /**
+ * Get the values of all footer lines with the given key.
+ *
+ * @param footers
+ * list of footers to find the values in.
+ * @param key
+ * 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 #fromMessage
+ * @since 6.7
+ */
+ public static List<String> getValues(List<FooterLine> footers, FooterKey key) {
+ if (footers.isEmpty())
+ return Collections.emptyList();
+ ArrayList<String> r = new ArrayList<>(footers.size());
+ for (FooterLine f : footers) {
+ if (f.matches(key))
+ r.add(f.getValue());
+ }
+ return r;
+ }
+
+ /**
* Whether keys match
*
* @param key
@@ -90,7 +224,7 @@ public final class FooterLine {
* character encoding.
*/
public String getValue() {
- return RawParseUtils.decode(enc, buffer, valStart, valEnd);
+ return RawParseUtils.decode(enc, buffer, valStart, valEnd).replaceAll("\n +", " "); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
@@ -117,7 +251,28 @@ public final class FooterLine {
return RawParseUtils.decode(enc, buffer, lt, gt - 1);
}
- /** {@inheritDoc} */
+ /**
+ * @return start offset of the footer relative to the original raw message
+ * byte buffer
+ *
+ * @see #fromMessage(byte[])
+ * @since 6.9
+ */
+ public int getStartOffset() {
+ return keyStart;
+ }
+
+ /**
+ * @return end offset of the footer relative to the original raw message
+ * byte buffer
+ *
+ * @see #fromMessage(byte[])
+ * @since 6.9
+ */
+ public int getEndOffset() {
+ return valEnd;
+ }
+
@Override
public String toString() {
return getKey() + ": " + getValue(); //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/Generator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/Generator.java
index 3493dcfb47..d97812ff45 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/Generator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/Generator.java
@@ -71,8 +71,11 @@ abstract class Generator {
*
* @return next available commit; null if no more are to be returned.
* @throws MissingObjectException
+ * if an object is missing
* @throws IncorrectObjectTypeException
+ * if an object has an unexpected type
* @throws IOException
+ * if an IO error occurred
*/
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
index 4773ca8427..ae67ca7599 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/LIFORevQueue.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/LIFORevQueue.java
@@ -34,7 +34,6 @@ public class LIFORevQueue extends BlockRevQueue {
super(s);
}
- /** {@inheritDoc} */
@Override
public void add(RevCommit c) {
Block b = head;
@@ -47,7 +46,6 @@ public class LIFORevQueue extends BlockRevQueue {
b.unpop(c);
}
- /** {@inheritDoc} */
@Override
public RevCommit next() {
final Block b = head;
@@ -62,7 +60,6 @@ public class LIFORevQueue extends BlockRevQueue {
return c;
}
- /** {@inheritDoc} */
@Override
public void clear() {
head = null;
@@ -89,7 +86,6 @@ public class LIFORevQueue extends BlockRevQueue {
return false;
}
- /** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder s = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
index a213dd47c6..be29dc3138 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/MergeBaseGenerator.java
@@ -12,7 +12,7 @@ package org.eclipse.jgit.revwalk;
import java.io.IOException;
import java.text.MessageFormat;
-import java.util.LinkedList;
+import java.util.ArrayDeque;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -47,7 +47,8 @@ class MergeBaseGenerator extends Generator {
private int recarryTest;
private int recarryMask;
private int mergeBaseAncestor = -1;
- private LinkedList<RevCommit> ret = new LinkedList<>();
+
+ private ArrayDeque<RevCommit> ret = new ArrayDeque<>();
private CarryStack stack;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
index 4e48a5c328..7c763bc9b8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java
@@ -43,7 +43,7 @@ import org.eclipse.jgit.util.RawParseUtils;
* 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.
+ * commits that reference them and the order they appear within a tree.
* Ordering can be affected by changing the
* {@link org.eclipse.jgit.revwalk.RevSort} used to order the commits that are
* returned first.
@@ -164,29 +164,6 @@ public class ObjectWalk extends RevWalk {
}
/**
- * Create an object reachability checker that will use bitmaps if possible.
- *
- * This reachability checker accepts any object as target. For checks
- * exclusively between commits, see
- * {@link RevWalk#createReachabilityChecker()}.
- *
- * @return an object reachability checker, using bitmaps if possible.
- *
- * @throws IOException
- * when the index fails to load.
- *
- * @since 5.8
- * @deprecated use
- * {@code ObjectReader#createObjectReachabilityChecker(ObjectWalk)}
- * instead.
- */
- @Deprecated
- public final ObjectReachabilityChecker createObjectReachabilityChecker()
- throws IOException {
- return reader.createObjectReachabilityChecker(this);
- }
-
- /**
* Mark an object or commit to start graph traversal from.
* <p>
* Callers are encouraged to use
@@ -300,14 +277,12 @@ public class ObjectWalk extends RevWalk {
addObject(o);
}
- /** {@inheritDoc} */
@Override
public void sort(RevSort s) {
super.sort(s);
boundary = hasRevSort(RevSort.BOUNDARY);
}
- /** {@inheritDoc} */
@Override
public void sort(RevSort s, boolean use) {
super.sort(s, use);
@@ -357,7 +332,6 @@ public class ObjectWalk extends RevWalk {
visitationPolicy = requireNonNull(policy);
}
- /** {@inheritDoc} */
@Override
public RevCommit next() throws MissingObjectException,
IncorrectObjectTypeException, IOException {
@@ -645,6 +619,8 @@ public class ObjectWalk extends RevWalk {
}
/**
+ * Get the current traversal depth from the root tree object
+ *
* @return the current traversal depth from the root tree object
* @since 5.4
*/
@@ -762,7 +738,6 @@ public class ObjectWalk extends RevWalk {
pathBuf = newBuf;
}
- /** {@inheritDoc} */
@Override
public void dispose() {
super.dispose();
@@ -771,7 +746,6 @@ public class ObjectWalk extends RevWalk {
freeVisit = null;
}
- /** {@inheritDoc} */
@Override
protected void reset(int retainFlags) {
super.reset(retainFlags);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ReachabilityChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ReachabilityChecker.java
index d499084b42..5afb669a15 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ReachabilityChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ReachabilityChecker.java
@@ -26,42 +26,8 @@ import org.eclipse.jgit.errors.MissingObjectException;
* @since 5.4
*/
public interface ReachabilityChecker {
-
- /**
- * Check if all targets are reachable from the {@code starter} commits.
- * <p>
- * Caller should parse the objectIds (preferably with
- * {@code walk.parseCommit()} and handle missing/incorrect type objects
- * before calling this method.
- *
- * @param targets
- * commits to reach.
- * @param starters
- * known starting points.
- * @return An unreachable target if at least one of the targets is
- * unreachable. An empty optional if all targets are reachable from
- * the starters.
- *
- * @throws MissingObjectException
- * if any of the incoming objects doesn't exist in the
- * repository.
- * @throws IncorrectObjectTypeException
- * if any of the incoming objects is not a commit or a tag.
- * @throws IOException
- * if any of the underlying indexes or readers can not be
- * opened.
- *
- * @deprecated see {{@link #areAllReachable(Collection, Stream)}
- */
- @Deprecated
- default Optional<RevCommit> areAllReachable(Collection<RevCommit> targets,
- Collection<RevCommit> starters) throws MissingObjectException,
- IncorrectObjectTypeException, IOException {
- return areAllReachable(targets, starters.stream());
- }
-
/**
- * Check if all targets are reachable from the {@code starter} commits.
+ * Check if all targets are reachable from the {@code starters} commits.
* <p>
* Caller should parse the objectIds (preferably with
* {@code walk.parseCommit()} and handle missing/incorrect type objects
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RenameCallback.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RenameCallback.java
index ba3399ce9f..9856f2c252 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RenameCallback.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RenameCallback.java
@@ -23,8 +23,30 @@ public abstract class RenameCallback {
* Called whenever a diff was found that is actually a rename or copy of a
* file.
*
+ * <p>Subclass of this class have to override this to receive diffEntry for
+ * the rename.
+ *
* @param entry
* the entry representing the rename/copy
*/
public abstract void renamed(DiffEntry entry);
+
+ /**
+ * Called whenever a diff was found that is actually a rename or copy of a
+ * file along with the commit that caused it.
+ *
+ * <p>Subclass of this class have an option to override this if it wants to
+ * know what commit generated the diffEntry. Otherwise defaults to the
+ * {@link RenameCallback#renamed(DiffEntry)} function.
+ *
+ * @param entry
+ * the entry representing the rename/copy
+ * @param commit
+ * commit at which callback occurred
+ *
+ * @since 6.7
+ */
+ public void renamed(DiffEntry entry, RevCommit commit) {
+ renamed(entry);
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevBlob.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevBlob.java
index f5abbb870a..1f81b6a922 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevBlob.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevBlob.java
@@ -33,7 +33,6 @@ public class RevBlob extends RevObject {
super(id);
}
- /** {@inheritDoc} */
@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
index b64c9ce906..871545fca2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
@@ -1,6 +1,6 @@
/*
- * Copyright (C) 2008-2009, Google Inc.
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2008, 2009 Google Inc.
+ * Copyright (C) 2008, 2024 Shawn O. Pearce <spearce@spearce.org> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -11,20 +11,18 @@
package org.eclipse.jgit.revwalk;
-import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.util.RawParseUtils.guessEncoding;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.storage.commitgraph.ChangedPathFilter;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.MutableObjectId;
@@ -222,7 +220,6 @@ public class RevCommit extends RevObject {
flags |= PARSED;
}
- /** {@inheritDoc} */
@Override
public final int getType() {
return Constants.OBJ_COMMIT;
@@ -404,14 +401,14 @@ public class RevCommit extends RevObject {
* @since 5.1
*/
public final byte[] getRawGpgSignature() {
- final byte[] raw = buffer;
- final byte[] header = { 'g', 'p', 'g', 's', 'i', 'g' };
- final int start = RawParseUtils.headerStart(header, raw, 0);
+ byte[] raw = buffer;
+ byte[] header = { 'g', 'p', 'g', 's', 'i', 'g' };
+ int start = RawParseUtils.headerStart(header, raw, 0);
if (start < 0) {
return null;
}
- final int end = RawParseUtils.headerEnd(raw, start);
- return Arrays.copyOfRange(raw, start, end);
+ int end = RawParseUtils.nextLfSkippingSplitLines(raw, start);
+ return RawParseUtils.headerValue(raw, start, end);
}
/**
@@ -484,7 +481,8 @@ public class RevCommit extends RevObject {
if (msgB < 0) {
return ""; //$NON-NLS-1$
}
- return RawParseUtils.decode(guessEncoding(), raw, msgB, raw.length);
+ return RawParseUtils.decode(guessEncoding(buffer), raw, msgB,
+ raw.length);
}
/**
@@ -510,7 +508,8 @@ public class RevCommit extends RevObject {
}
int msgE = RawParseUtils.endOfParagraph(raw, msgB);
- String str = RawParseUtils.decode(guessEncoding(), raw, msgB, msgE);
+ String str = RawParseUtils.decode(guessEncoding(buffer), raw, msgB,
+ msgE);
if (hasLF(raw, msgB, msgE)) {
str = StringUtils.replaceLineBreaksWithSpace(str);
}
@@ -525,6 +524,30 @@ public class RevCommit extends RevObject {
}
/**
+ * Parse the commit message and return its first line, i.e., everything up
+ * to but not including the first newline, if any.
+ *
+ * @return the first line of the decoded commit message as a string; never
+ * {@code null}.
+ * @since 7.2
+ */
+ public final String getFirstMessageLine() {
+ int msgB = RawParseUtils.commitMessage(buffer, 0);
+ if (msgB < 0) {
+ return ""; //$NON-NLS-1$
+ }
+ int msgE = msgB;
+ byte[] raw = buffer;
+ while (msgE < raw.length && raw[msgE] != '\n') {
+ msgE++;
+ }
+ if (msgE > msgB && msgE > 0 && raw[msgE - 1] == '\r') {
+ msgE--;
+ }
+ return RawParseUtils.decode(guessEncoding(buffer), buffer, msgB, msgE);
+ }
+
+ /**
* Determine the encoding of the commit message buffer.
* <p>
* Locates the "encoding" header (if present) and returns its value. Due to
@@ -562,14 +585,6 @@ public class RevCommit extends RevObject {
return RawParseUtils.parseEncoding(buffer);
}
- private Charset guessEncoding() {
- try {
- return getEncoding();
- } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
- return UTF_8;
- }
- }
-
/**
* Parse the footer lines (e.g. "Signed-off-by") for machine processing.
* <p>
@@ -578,8 +593,9 @@ public class RevCommit extends RevObject {
* the order of the line's appearance in the commit message itself.
* <p>
* A footer line's key must match the pattern {@code ^[A-Za-z0-9-]+:}, while
- * the value is free-form, but must not contain an LF. Very common keys seen
- * in the wild are:
+ * the value is free-form. The Value may be split over multiple lines with
+ * each subsequent line starting with at least one whitespace. Very common
+ * keys seen in the wild are:
* <ul>
* <li>{@code Signed-off-by} (agrees to Developer Certificate of Origin)
* <li>{@code Acked-by} (thinks change looks sane in context)
@@ -592,50 +608,14 @@ public class RevCommit extends RevObject {
* @return ordered list of footer lines; empty list if no footers found.
*/
public final List<FooterLine> getFooterLines() {
- final byte[] raw = buffer;
- int ptr = raw.length - 1;
- while (raw[ptr] == '\n') // trim any trailing LFs, not interesting
- ptr--;
-
- final int msgB = RawParseUtils.commitMessage(raw, 0);
- final ArrayList<FooterLine> r = new ArrayList<>(4);
- final Charset enc = guessEncoding();
- 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;
+ return FooterLine.fromMessage(buffer);
}
/**
* Get the values of all footer lines with the given key.
*
* @param keyName
- * footer key to find values of, case insensitive.
+ * 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
@@ -643,30 +623,22 @@ public class RevCommit extends RevObject {
* @see #getFooterLines()
*/
public final List<String> getFooterLines(String keyName) {
- return getFooterLines(new FooterKey(keyName));
+ return FooterLine.getValues(getFooterLines(), keyName);
}
/**
* Get the values of all footer lines with the given key.
*
- * @param keyName
- * footer key to find values of, case insensitive.
+ * @param key
+ * footer key to find values of, case-insensitive.
* @return values of footers with key of {@code keyName}, ordered by their
* order of appearance. Duplicates may be returned if the same
* footer appeared more than once. Empty list if no footers appear
* with the specified key, or there are no footers at all.
* @see #getFooterLines()
*/
- public final List<String> getFooterLines(FooterKey keyName) {
- final List<FooterLine> src = getFooterLines();
- if (src.isEmpty())
- return Collections.emptyList();
- final ArrayList<String> r = new ArrayList<>(src.size());
- for (FooterLine f : src) {
- if (f.matches(keyName))
- r.add(f.getValue());
- }
- return r;
+ public final List<String> getFooterLines(FooterKey key) {
+ return FooterLine.getValues(getFooterLines(), key);
}
/**
@@ -688,6 +660,21 @@ public class RevCommit extends RevObject {
}
/**
+ * Get the changed path filter of the commit.
+ * <p>
+ * This is null when there is no commit graph file, the commit is not in the
+ * commit graph file, or the commit graph file was generated without changed
+ * path filters.
+ *
+ * @param rw A revwalk to load the commit graph (if available)
+ * @return the changed path filter
+ * @since 6.7
+ */
+ public ChangedPathFilter getChangedPathFilter(RevWalk rw) {
+ return null;
+ }
+
+ /**
* Reset this commit to allow another RevWalk with the same instances.
* <p>
* Subclasses <b>must</b> call <code>super.reset()</code> to ensure the
@@ -713,7 +700,6 @@ public class RevCommit extends RevObject {
buffer = null;
}
- /** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder s = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitCG.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitCG.java
index 4d3664da11..c7a03992b7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitCG.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitCG.java
@@ -14,6 +14,7 @@ import java.io.IOException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.storage.commitgraph.ChangedPathFilter;
import org.eclipse.jgit.internal.storage.commitgraph.CommitGraph;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
@@ -44,7 +45,6 @@ class RevCommitCG extends RevCommit {
this.graphPosition = graphPosition;
}
- /** {@inheritDoc} */
@Override
void parseCanonical(RevWalk walk, byte[] raw) throws IOException {
if (walk.isRetainBody()) {
@@ -53,7 +53,6 @@ class RevCommitCG extends RevCommit {
parseInGraph(walk);
}
- /** {@inheritDoc} */
@Override
void parseHeaders(RevWalk walk) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
@@ -98,9 +97,14 @@ class RevCommitCG extends RevCommit {
flags |= PARSED;
}
- /** {@inheritDoc} */
@Override
int getGeneration() {
return generation;
}
+
+ /** {@inheritDoc} */
+ @Override
+ public ChangedPathFilter getChangedPathFilter(RevWalk rw) {
+ return rw.commitGraph().getChangedPathFilter(graphPosition);
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitList.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitList.java
index 59213a8e60..58f5d425cc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommitList.java
@@ -25,7 +25,6 @@ import org.eclipse.jgit.revwalk.filter.RevFilter;
public class RevCommitList<E extends RevCommit> extends RevObjectList<E> {
private RevWalk walker;
- /** {@inheritDoc} */
@Override
public void clear() {
super.clear();
@@ -313,7 +312,6 @@ public class RevCommitList<E extends RevCommit> extends RevObjectList<E> {
* walker specified by {@link #source(RevWalk)} is pumped until the
* specified commit is loaded. Callers can test the final size of the list
* by {@link #size()} to determine if the high water mark specified was met.
- * <p>
*
* @param commitToLoad
* commit the caller wants this list to contain when the fill
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java
index a0160ddbef..3221bf6e8f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevFlag.java
@@ -67,7 +67,6 @@ public class RevFlag {
return walker;
}
- /** {@inheritDoc} */
@Override
public String toString() {
return name;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java
index 4d2684a0f0..55ea30d24b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObject.java
@@ -132,7 +132,6 @@ public abstract class RevObject extends ObjectIdOwnerMap.Entry {
flags &= ~set.mask;
}
- /** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder s = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java
index 0393f55957..ad8aeedeb9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevObjectList.java
@@ -47,7 +47,6 @@ public class RevObjectList<E extends RevObject> extends AbstractList<E> {
// Initialized above.
}
- /** {@inheritDoc} */
@Override
public void add(int index, E element) {
if (index != size)
@@ -58,7 +57,6 @@ public class RevObjectList<E extends RevObject> extends AbstractList<E> {
size++;
}
- /** {@inheritDoc} */
@Override
@SuppressWarnings("unchecked")
public E set(int index, E element) {
@@ -80,7 +78,6 @@ public class RevObjectList<E extends RevObject> extends AbstractList<E> {
return (E) old;
}
- /** {@inheritDoc} */
@Override
@SuppressWarnings("unchecked")
public E get(int index) {
@@ -95,13 +92,11 @@ public class RevObjectList<E extends RevObject> extends AbstractList<E> {
return s != null ? (E) s.contents[index] : null;
}
- /** {@inheritDoc} */
@Override
public int size() {
return size;
}
- /** {@inheritDoc} */
@Override
public void clear() {
contents = new Block(0);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
index b9d145008b..0737a78085 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
@@ -38,8 +38,17 @@ import org.eclipse.jgit.util.StringUtils;
*/
public class RevTag extends RevObject {
- private static final byte[] hSignature = Constants
- .encodeASCII("-----BEGIN PGP SIGNATURE-----"); //$NON-NLS-1$
+ private static final byte[] SIGNATURE_START = Constants
+ .encodeASCII("-----BEGIN"); //$NON-NLS-1$
+
+ private static final byte[] GPG_SIGNATURE_START = Constants
+ .encodeASCII(Constants.GPG_SIGNATURE_PREFIX);
+
+ private static final byte[] CMS_SIGNATURE_START = Constants
+ .encodeASCII(Constants.CMS_SIGNATURE_PREFIX);
+
+ private static final byte[] SSH_SIGNATURE_START = Constants
+ .encodeASCII(Constants.SSH_SIGNATURE_PREFIX);
/**
* Parse an annotated tag from its canonical format.
@@ -149,7 +158,6 @@ public class RevTag extends RevObject {
flags |= PARSED;
}
- /** {@inheritDoc} */
@Override
public final int getType() {
return Constants.OBJ_TAG;
@@ -209,20 +217,27 @@ public class RevTag extends RevObject {
return msgB;
}
// Find the last signature start and return the rest
- int start = nextStart(hSignature, raw, msgB);
+ int start = nextStart(SIGNATURE_START, raw, msgB);
if (start < 0) {
return start;
}
int next = RawParseUtils.nextLF(raw, start);
while (next < raw.length) {
- int newStart = nextStart(hSignature, raw, next);
+ int newStart = nextStart(SIGNATURE_START, raw, next);
if (newStart < 0) {
break;
}
start = newStart;
next = RawParseUtils.nextLF(raw, start);
}
- return start;
+ // SIGNATURE_START is just a prefix. Check that it is one of the known
+ // full signature start tags.
+ if (RawParseUtils.match(raw, start, GPG_SIGNATURE_START) > 0
+ || RawParseUtils.match(raw, start, CMS_SIGNATURE_START) > 0
+ || RawParseUtils.match(raw, start, SSH_SIGNATURE_START) > 0) {
+ return start;
+ }
+ return -1;
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTree.java
index 8119af468a..d81af0c029 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTree.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTree.java
@@ -33,7 +33,6 @@ public class RevTree extends RevObject {
super(id);
}
- /** {@inheritDoc} */
@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
index 3737c6b67b..41f98bad84 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -19,8 +19,14 @@ import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import java.util.
+Optional;
+import java.util.Set;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
@@ -30,9 +36,9 @@ import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.RevWalkException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.commitgraph.CommitGraph;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.AsyncObjectLoaderQueue;
-import org.eclipse.jgit.internal.storage.commitgraph.CommitGraph;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.NullProgressMonitor;
@@ -220,7 +226,6 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
/**
* Create a new revision walker for a given repository.
- * <p>
*
* @param or
* the reader the walker will obtain data from. The reader is not
@@ -236,7 +241,7 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
idBuffer = new MutableObjectId();
objects = new ObjectIdOwnerMap<>();
roots = new ArrayList<>();
- queue = new DateRevQueue(false);
+ queue = newDateRevQueue(false);
pending = new StartGenerator(this);
sorting = EnumSet.of(RevSort.NONE);
filter = RevFilter.ALL;
@@ -245,6 +250,30 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
commitGraph = null;
}
+ static AbstractRevQueue newDateRevQueue(boolean firstParent) {
+ if(usePriorityQueue()) {
+ return new DateRevPriorityQueue(firstParent);
+ }
+
+ return new DateRevQueue(firstParent);
+ }
+
+ static DateRevQueue newDateRevQueue(Generator g) throws IOException {
+ if(usePriorityQueue()) {
+ return new DateRevPriorityQueue(g);
+ }
+
+ return new DateRevQueue(g);
+ }
+
+ @SuppressWarnings("boxing")
+ private static boolean usePriorityQueue() {
+ return Optional
+ .ofNullable(System.getProperty("REVWALK_USE_PRIORITY_QUEUE")) //$NON-NLS-1$
+ .map(Boolean::parseBoolean)
+ .orElse(false);
+ }
+
/**
* Get the reader this walker is using to load objects.
*
@@ -255,23 +284,6 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
}
/**
- * Get a reachability checker for commits over this revwalk.
- *
- * @return the most efficient reachability checker for this repository.
- * @throws IOException
- * if it cannot open any of the underlying indices.
- *
- * @since 5.4
- * @deprecated use {@code ObjectReader#createReachabilityChecker(RevWalk)}
- * instead.
- */
- @Deprecated
- public final ReachabilityChecker createReachabilityChecker()
- throws IOException {
- return reader.createReachabilityChecker(this);
- }
-
- /**
* {@inheritDoc}
* <p>
* Release any resources used by this walker's reader.
@@ -454,7 +466,6 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
* <p>
* A commit is merged into a ref if we can find a path of commits that leads
* from that specific ref and ends at <code>commit</code>.
- * <p>
*
* @param commit
* commit the caller thinks is reachable from <code>refs</code>.
@@ -476,7 +487,6 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
* <p>
* A commit is merged into a ref if we can find a path of commits that leads
* from that specific ref and ends at <code>commit</code>.
- * <p>
*
* @param commit
* commit the caller thinks is reachable from <code>refs</code>.
@@ -518,6 +528,27 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
}
/**
+ * Determine if a <code>commit</code> is merged into any of the given
+ * <code>revs</code>.
+ *
+ * @param commit
+ * commit the caller thinks is reachable from <code>revs</code>.
+ * @param revs
+ * commits to start iteration from, and which is most likely a
+ * descendant (child) of <code>commit</code>.
+ * @return true if commit is merged into any of the revs; false otherwise.
+ * @throws java.io.IOException
+ * a pack file or loose object could not be read.
+ * @since 6.10.1
+ */
+ public boolean isMergedIntoAnyCommit(RevCommit commit, Collection<RevCommit> revs)
+ throws IOException {
+ return getCommitsMergedInto(commit, revs,
+ GetMergedIntoStrategy.RETURN_ON_FIRST_FOUND,
+ NullProgressMonitor.INSTANCE).size() > 0;
+ }
+
+ /**
* Determine if a <code>commit</code> is merged into all of the given
* <code>refs</code>.
*
@@ -540,7 +571,26 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
private List<Ref> getMergedInto(RevCommit needle, Collection<Ref> haystacks,
Enum returnStrategy, ProgressMonitor monitor) throws IOException {
+ Map<RevCommit, List<Ref>> refsByCommit = new HashMap<>();
+ for (Ref r : haystacks) {
+ RevObject o = peel(parseAny(r.getObjectId()));
+ if (!(o instanceof RevCommit)) {
+ continue;
+ }
+ refsByCommit.computeIfAbsent((RevCommit) o, c -> new ArrayList<>()).add(r);
+ }
+ monitor.update(1);
List<Ref> result = new ArrayList<>();
+ for (RevCommit c : getCommitsMergedInto(needle, refsByCommit.keySet(),
+ returnStrategy, monitor)) {
+ result.addAll(refsByCommit.get(c));
+ }
+ return result;
+ }
+
+ private Set<RevCommit> getCommitsMergedInto(RevCommit needle, Collection<RevCommit> haystacks,
+ Enum returnStrategy, ProgressMonitor monitor) throws IOException {
+ Set<RevCommit> result = new HashSet<>();
List<RevCommit> uninteresting = new ArrayList<>();
List<RevCommit> marked = new ArrayList<>();
RevFilter oldRF = filter;
@@ -556,16 +606,11 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
needle.parseHeaders(this);
}
int cutoff = needle.getGeneration();
- for (Ref r : haystacks) {
+ for (RevCommit c : haystacks) {
if (monitor.isCancelled()) {
return result;
}
monitor.update(1);
- RevObject o = peel(parseAny(r.getObjectId()));
- if (!(o instanceof RevCommit)) {
- continue;
- }
- RevCommit c = (RevCommit) o;
reset(UNINTERESTING | TEMP_MARK);
markStart(c);
boolean commitFound = false;
@@ -577,7 +622,7 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
}
if (References.isSameObject(next, needle)
|| (next.flags & TEMP_MARK) != 0) {
- result.add(r);
+ result.add(c);
if (returnStrategy == GetMergedIntoStrategy.RETURN_ON_FIRST_FOUND) {
return result;
}
@@ -824,6 +869,8 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
}
/**
+ * Whether only first-parent links should be followed when walking
+ *
* @return whether only first-parent links should be followed when walking.
*
* @since 5.5
@@ -848,7 +895,7 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
assertNotStarted();
assertNoCommitsMarkedStart();
firstParent = enable;
- queue = new DateRevQueue(firstParent);
+ queue = newDateRevQueue(firstParent);
pending = new StartGenerator(this);
}
@@ -1197,6 +1244,8 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
/**
* Asynchronous object parsing.
*
+ * @param <T>
+ * Type of returned {@code ObjectId}
* @param objectIds
* objects to open from the object store. The supplied collection
* must not be modified until the queue has finished.
@@ -1566,7 +1615,7 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
}
roots.clear();
- queue = new DateRevQueue(firstParent);
+ queue = newDateRevQueue(firstParent);
pending = new StartGenerator(this);
}
@@ -1587,7 +1636,7 @@ public class RevWalk implements Iterable<RevCommit>, AutoCloseable {
firstParent = false;
objects.clear();
roots.clear();
- queue = new DateRevQueue(firstParent);
+ queue = newDateRevQueue(firstParent);
pending = new StartGenerator(this);
shallowCommitsInitialized = false;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java
index e52e916318..4bfe2fda02 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalkUtils.java
@@ -51,8 +51,11 @@ public final class RevWalkUtils {
* should be done until there are no more commits
* @return the number of commits
* @throws org.eclipse.jgit.errors.MissingObjectException
+ * if object is missing
* @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
+ * if object has unexpected type
* @throws java.io.IOException
+ * if an IO error occurred
*/
public static int count(final RevWalk walk, final RevCommit start,
final RevCommit end) throws MissingObjectException,
@@ -80,8 +83,11 @@ public final class RevWalkUtils {
* should be done until there are no more commits
* @return the commits found
* @throws org.eclipse.jgit.errors.MissingObjectException
+ * if object is missing
* @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
+ * if object has unexpected type
* @throws java.io.IOException
+ * if an IO error occurred
*/
public static List<RevCommit> find(final RevWalk walk,
final RevCommit start, final RevCommit end)
@@ -116,8 +122,11 @@ public final class RevWalkUtils {
* the set of branches we want to see reachability from
* @return the list of branches a given commit is reachable from
* @throws org.eclipse.jgit.errors.MissingObjectException
+ * if object is missing
* @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
+ * if object has unexpected type
* @throws java.io.IOException
+ * if an IO error occurred
*/
public static List<Ref> findBranchesReachableFrom(RevCommit commit,
RevWalk revWalk, Collection<Ref> refs)
@@ -147,8 +156,11 @@ public final class RevWalkUtils {
* the callback for progress and cancellation
* @return the list of branches a given commit is reachable from
* @throws org.eclipse.jgit.errors.MissingObjectException
+ * if object is missing
* @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
+ * if object has unexpected type
* @throws java.io.IOException
+ * if an IO error occurred
* @since 5.4
*/
public static List<Ref> findBranchesReachableFrom(RevCommit commit,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java
index 2c88bb872e..4a93f4d745 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java
@@ -101,9 +101,13 @@ class RewriteGenerator extends Generator {
* of this commit by the previous {@link PendingGenerator}.
*
* @param c
+ * given commit
* @throws MissingObjectException
+ * if an object is missing
* @throws IncorrectObjectTypeException
+ * if an object has an unexpected type
* @throws IOException
+ * if an IO error occurred
*/
private void applyFilterToParents(RevCommit c)
throws MissingObjectException, IncorrectObjectTypeException,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java
index a79901ca10..6854b6083d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/StartGenerator.java
@@ -93,10 +93,11 @@ class StartGenerator extends Generator {
final DateRevQueue pending;
int pendingOutputType = 0;
- if (q instanceof DateRevQueue)
+ if (q instanceof DateRevQueue) {
pending = (DateRevQueue)q;
- else
- pending = new DateRevQueue(q);
+ } else {
+ pending = RevWalk.newDateRevQueue(q);
+ }
if (tf != TreeFilter.ALL) {
int rewriteFlag;
if (w.getRewriteParents()) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoNonIntermixSortGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoNonIntermixSortGenerator.java
index 452545a04e..c9d8ff90fd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoNonIntermixSortGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoNonIntermixSortGenerator.java
@@ -33,8 +33,11 @@ class TopoNonIntermixSortGenerator extends Generator {
* @param s
* generator to pull all commits out of, and into this buffer.
* @throws MissingObjectException
+ * if an object is missing
* @throws IncorrectObjectTypeException
+ * if an object has an unexpected type
* @throws IOException
+ * if an IO error occurred
*/
TopoNonIntermixSortGenerator(Generator s) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java
index 4739f78fc0..950b0a45b4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java
@@ -33,8 +33,11 @@ class TopoSortGenerator extends Generator {
* @param s
* generator to pull all commits out of, and into this buffer.
* @throws MissingObjectException
+ * if an object is missing
* @throws IncorrectObjectTypeException
+ * if an object has an unexpected type
* @throws IOException
+ * if an IO error occurred
*/
TopoSortGenerator(Generator s) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java
index f921449ffa..e9a3e72c7f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java
@@ -17,7 +17,6 @@ import org.eclipse.jgit.diff.DiffConfig;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.diff.RenameDetector;
-import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.StopWalkException;
@@ -25,6 +24,7 @@ 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;
+import org.eclipse.jgit.treewalk.filter.TreeFilter.MutableBoolean;
/**
* Filter applying a {@link org.eclipse.jgit.treewalk.filter.TreeFilter} against
@@ -47,6 +47,14 @@ public class TreeRevFilter extends RevFilter {
private final TreeWalk pathFilter;
+ private final MutableBoolean changedPathFilterUsed = new MutableBoolean();
+
+ private long changedPathFilterTruePositive = 0;
+
+ private long changedPathFilterFalsePositive = 0;
+
+ private long changedPathFilterNegative = 0;
+
/**
* Create a {@link org.eclipse.jgit.revwalk.filter.RevFilter} from a
* {@link org.eclipse.jgit.treewalk.filter.TreeFilter}.
@@ -92,13 +100,11 @@ public class TreeRevFilter extends RevFilter {
this.rewriteFlag = rewriteFlag;
}
- /** {@inheritDoc} */
@Override
public RevFilter clone() {
throw new UnsupportedOperationException();
}
- /** {@inheritDoc} */
@Override
public boolean include(RevWalk walker, RevCommit c)
throws StopWalkException, MissingObjectException,
@@ -119,17 +125,34 @@ public class TreeRevFilter extends RevFilter {
}
trees[nParents] = c.getTree();
tw.reset(trees);
+ changedPathFilterUsed.reset();
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.
+ TreeFilter tf = pathFilter.getFilter();
+ boolean mustCalculateChgs = tf.shouldTreeWalk(c, walker,
+ changedPathFilterUsed);
+ if (mustCalculateChgs) {
+ while (tw.next()) {
+ chgs++;
+ if (tw.getRawMode(0) == 0 && tw.getRawMode(1) != 0) {
+ adds++;
+ } else {
+ break; // no point in looking at this further.
+ }
+ }
+ if (changedPathFilterUsed.get()) {
+ if (chgs > 0) {
+ changedPathFilterTruePositive++;
+ } else {
+ changedPathFilterFalsePositive++;
+ }
+ }
+ } else {
+ if (changedPathFilterUsed.get()) {
+ changedPathFilterNegative++;
}
}
@@ -149,7 +172,8 @@ public class TreeRevFilter extends RevFilter {
// commit. We need to update our filter to its older
// name, if we can discover it. Find out what that is.
//
- updateFollowFilter(trees, ((FollowFilter) tw.getFilter()).cfg);
+ updateFollowFilter(trees, ((FollowFilter) tw.getFilter()).cfg,
+ c);
}
return true;
} else if (nParents == 0) {
@@ -241,15 +265,47 @@ public class TreeRevFilter extends RevFilter {
return false;
}
- /** {@inheritDoc} */
@Override
public boolean requiresCommitBody() {
return false;
}
- private void updateFollowFilter(ObjectId[] trees, DiffConfig cfg)
- throws MissingObjectException, IncorrectObjectTypeException,
- CorruptObjectException, IOException {
+ /**
+ * Return how many times a changed path filter correctly predicted that a
+ * path was changed in a commit, for statistics gathering purposes.
+ *
+ * @return count of true positives
+ * @since 6.7
+ */
+ public long getChangedPathFilterTruePositive() {
+ return changedPathFilterTruePositive;
+ }
+
+ /**
+ * Return how many times a changed path filter wrongly predicted that a path
+ * was changed in a commit, for statistics gathering purposes.
+ *
+ * @return count of false positives
+ * @since 6.7
+ */
+ public long getChangedPathFilterFalsePositive() {
+ return changedPathFilterFalsePositive;
+ }
+
+ /**
+ * Return how many times a changed path filter predicted that a path was not
+ * changed in a commit (allowing that commit to be skipped), for statistics
+ * gathering purposes.
+ *
+ * @return count of negatives
+ * @since 6.7
+ */
+ public long getChangedPathFilterNegative() {
+ return changedPathFilterNegative;
+ }
+
+ private void updateFollowFilter(ObjectId[] trees, DiffConfig cfg,
+ RevCommit commit) throws IOException {
TreeWalk tw = pathFilter;
FollowFilter oldFilter = (FollowFilter) tw.getFilter();
tw.setFilter(TreeFilter.ANY_DIFF);
@@ -266,7 +322,7 @@ public class TreeRevFilter extends RevFilter {
newFilter = FollowFilter.create(ent.getOldPath(), cfg);
RenameCallback callback = oldFilter.getRenameCallback();
if (callback != null) {
- callback.renamed(ent);
+ callback.renamed(ent, commit);
// forward the callback to the new follow filter
((FollowFilter) newFilter).setRenameCallback(callback);
}
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
index 6ac490a345..c9186b5491 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/CommitTimeRevFilter.java
@@ -12,6 +12,7 @@
package org.eclipse.jgit.revwalk.filter;
import java.io.IOException;
+import java.time.Instant;
import java.util.Date;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -30,9 +31,24 @@ public abstract class CommitTimeRevFilter extends RevFilter {
* @param ts
* the point in time to cut on.
* @return a new filter to select commits on or before <code>ts</code>.
+ *
+ * @deprecated Use {@link #before(Instant)} instead.
*/
+ @Deprecated(since="7.2")
public static final RevFilter before(Date ts) {
- return before(ts.getTime());
+ return before(ts.toInstant());
+ }
+
+ /**
+ * Create a new filter to select commits before a given date/time.
+ *
+ * @param ts
+ * the point in time to cut on.
+ * @return a new filter to select commits on or before <code>ts</code>.
+ * @since 7.2
+ */
+ public static RevFilter before(Instant ts) {
+ return new Before(ts);
}
/**
@@ -43,7 +59,7 @@ public abstract class CommitTimeRevFilter extends RevFilter {
* @return a new filter to select commits on or before <code>ts</code>.
*/
public static final RevFilter before(long ts) {
- return new Before(ts);
+ return new Before(Instant.ofEpochMilli(ts));
}
/**
@@ -52,9 +68,24 @@ public abstract class CommitTimeRevFilter extends RevFilter {
* @param ts
* the point in time to cut on.
* @return a new filter to select commits on or after <code>ts</code>.
+ *
+ * @deprecated Use {@link #after(Instant)} instead.
*/
+ @Deprecated(since="7.2")
public static final RevFilter after(Date ts) {
- return after(ts.getTime());
+ return after(ts.toInstant());
+ }
+
+ /**
+ * Create a new filter to select commits after a given date/time.
+ *
+ * @param ts
+ * the point in time to cut on.
+ * @return a new filter to select commits on or after <code>ts</code>.
+ * @since 7.2
+ */
+ public static RevFilter after(Instant ts) {
+ return new After(ts);
}
/**
@@ -65,7 +96,7 @@ public abstract class CommitTimeRevFilter extends RevFilter {
* @return a new filter to select commits on or after <code>ts</code>.
*/
public static final RevFilter after(long ts) {
- return new After(ts);
+ return after(Instant.ofEpochMilli(ts));
}
/**
@@ -75,9 +106,28 @@ public abstract class CommitTimeRevFilter extends RevFilter {
* @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.
+ *
+ * @deprecated Use {@link #between(Instant, Instant)} instead.
*/
+ @Deprecated(since="7.2")
public static final RevFilter between(Date since, Date until) {
- return between(since.getTime(), until.getTime());
+ return between(since.toInstant(), until.toInstant());
+ }
+
+ /**
+ * Create a new filter to select commits after or equal a given date/time
+ * <code>since</code> and before or equal a given date/time
+ * <code>until</code>.
+ *
+ * @param since
+ * the point in time to cut on.
+ * @param until
+ * the point in time to cut off.
+ * @return a new filter to select commits between the given date/times.
+ * @since 7.2
+ */
+ public static RevFilter between(Instant since, Instant until) {
+ return new Between(since, until);
}
/**
@@ -87,9 +137,12 @@ public abstract class CommitTimeRevFilter extends RevFilter {
* @param since the point in time to cut on, in milliseconds.
* @param until the point in time to cut off, in millisconds.
* @return a new filter to select commits between the given date/times.
+ *
+ * @deprecated Use {@link #between(Instant, Instant)} instead.
*/
+ @Deprecated(since="7.2")
public static final RevFilter between(long since, long until) {
- return new Between(since, until);
+ return new Between(Instant.ofEpochMilli(since), Instant.ofEpochMilli(until));
}
final int when;
@@ -98,21 +151,23 @@ public abstract class CommitTimeRevFilter extends RevFilter {
when = (int) (ts / 1000);
}
- /** {@inheritDoc} */
+ CommitTimeRevFilter(Instant t) {
+ when = (int) t.getEpochSecond();
+ }
+
@Override
public RevFilter clone() {
return this;
}
- /** {@inheritDoc} */
@Override
public boolean requiresCommitBody() {
return false;
}
private static class Before extends CommitTimeRevFilter {
- Before(long ts) {
- super(ts);
+ Before(Instant t) {
+ super(t);
}
@Override
@@ -125,14 +180,12 @@ public abstract class CommitTimeRevFilter extends RevFilter {
@SuppressWarnings("nls")
@Override
public String toString() {
- return super.toString() + "(" + new Date(when * 1000L) + ")";
+ return super.toString() + "(" + Instant.ofEpochSecond(when) + ")";
}
}
private static class After extends CommitTimeRevFilter {
- After(long ts) {
- super(ts);
- }
+ After(Instant t) { super(t); }
@Override
public boolean include(RevWalk walker, RevCommit cmit)
@@ -150,16 +203,16 @@ public abstract class CommitTimeRevFilter extends RevFilter {
@SuppressWarnings("nls")
@Override
public String toString() {
- return super.toString() + "(" + new Date(when * 1000L) + ")";
+ return super.toString() + "(" + Instant.ofEpochSecond(when) + ")";
}
}
private static class Between extends CommitTimeRevFilter {
private final int until;
- Between(long since, long until) {
+ Between(Instant since, Instant until) {
super(since);
- this.until = (int) (until / 1000);
+ this.until = (int) until.getEpochSecond();
}
@Override
@@ -172,8 +225,8 @@ public abstract class CommitTimeRevFilter extends RevFilter {
@SuppressWarnings("nls")
@Override
public String toString() {
- return super.toString() + "(" + new Date(when * 1000L) + " - "
- + new Date(until * 1000L) + ")";
+ return super.toString() + "(" + Instant.ofEpochSecond(when) + " - "
+ + Instant.ofEpochSecond(until) + ")";
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MaxCountRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MaxCountRevFilter.java
index 0e3a803b64..09bcb33f2e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MaxCountRevFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/MaxCountRevFilter.java
@@ -46,7 +46,6 @@ public class MaxCountRevFilter extends RevFilter {
this.maxCount = maxCount;
}
- /** {@inheritDoc} */
@Override
public boolean include(RevWalk walker, RevCommit cmit)
throws StopWalkException, MissingObjectException,
@@ -57,7 +56,6 @@ public class MaxCountRevFilter extends RevFilter {
return true;
}
- /** {@inheritDoc} */
@Override
public RevFilter clone() {
return new MaxCountRevFilter(maxCount);
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
index 1482c51291..8fcebc9fa1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/NotRevFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/NotRevFilter.java
@@ -38,13 +38,11 @@ public class NotRevFilter extends RevFilter {
a = one;
}
- /** {@inheritDoc} */
@Override
public RevFilter negate() {
return a;
}
- /** {@inheritDoc} */
@Override
public boolean include(RevWalk walker, RevCommit c)
throws MissingObjectException, IncorrectObjectTypeException,
@@ -52,19 +50,16 @@ public class NotRevFilter extends RevFilter {
return !a.include(walker, c);
}
- /** {@inheritDoc} */
@Override
public boolean requiresCommitBody() {
return a.requiresCommitBody();
}
- /** {@inheritDoc} */
@Override
public RevFilter clone() {
return new NotRevFilter(a.clone());
}
- /** {@inheritDoc} */
@Override
public String toString() {
return "NOT " + a.toString(); //$NON-NLS-1$
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
index 256e4794cd..8430646305 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFilter.java
@@ -264,7 +264,6 @@ public abstract class RevFilter {
@Override
public abstract RevFilter clone();
- /** {@inheritDoc} */
@Override
public String toString() {
String n = getClass().getName();
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
index 16d9f76143..f9ee632b50 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/RevFlagFilter.java
@@ -91,13 +91,11 @@ public abstract class RevFlagFilter extends RevFilter {
flags = m;
}
- /** {@inheritDoc} */
@Override
public RevFilter clone() {
return this;
}
- /** {@inheritDoc} */
@Override
public String toString() {
return super.toString() + flags;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java
index 84df36a2d2..bf93b7ba3e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SkipRevFilter.java
@@ -45,7 +45,6 @@ public class SkipRevFilter extends RevFilter {
this.skip = skip;
}
- /** {@inheritDoc} */
@Override
public boolean include(RevWalk walker, RevCommit cmit)
throws StopWalkException, MissingObjectException,
@@ -55,7 +54,6 @@ public class SkipRevFilter extends RevFilter {
return true;
}
- /** {@inheritDoc} */
@Override
public RevFilter clone() {
return new SkipRevFilter(skip);
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
index 7f00dfd504..ff1fea2418 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SubStringRevFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/filter/SubStringRevFilter.java
@@ -67,7 +67,6 @@ public abstract class SubStringRevFilter extends RevFilter {
pattern = new RawSubStringPattern(patternText);
}
- /** {@inheritDoc} */
@Override
public boolean include(RevWalk walker, RevCommit cmit)
throws MissingObjectException, IncorrectObjectTypeException,
@@ -75,7 +74,6 @@ public abstract class SubStringRevFilter extends RevFilter {
return pattern.match(text(cmit)) >= 0;
}
- /** {@inheritDoc} */
@Override
public boolean requiresCommitBody() {
return true;
@@ -90,13 +88,11 @@ public abstract class SubStringRevFilter extends RevFilter {
*/
protected abstract RawCharSequence text(RevCommit cmit);
- /** {@inheritDoc} */
@Override
public RevFilter clone() {
return this; // Typically we are actually thread-safe.
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
index cba5e1697c..3b7c02eca9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java
@@ -5,7 +5,7 @@
* Copyright (C) 2009, JetBrains s.r.o.
* Copyright (C) 2008-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
- * Copyright (C) 2008, Thad Hughes <thadh@thad.corp.google.com> and others
+ * Copyright (C) 2008, 2023 Thad Hughes <thadh@thad.corp.google.com> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -20,8 +20,11 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.MessageFormat;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.LockFailedException;
@@ -46,12 +49,18 @@ public class FileBasedConfig extends StoredConfig {
private final FS fs;
+ // In-process synchronization between load() and save().
+ private final ReentrantReadWriteLock lock;
+
private boolean utf8Bom;
private volatile FileSnapshot snapshot;
private volatile ObjectId hash;
+ private AtomicBoolean exists = new AtomicBoolean();
+
+
/**
* Create a configuration with no default fallback.
*
@@ -82,9 +91,9 @@ public class FileBasedConfig extends StoredConfig {
this.fs = fs;
this.snapshot = FileSnapshot.DIRTY;
this.hash = ObjectId.zeroId();
+ this.lock = new ReentrantReadWriteLock(false);
}
- /** {@inheritDoc} */
@Override
protected boolean notifyUponTransientChanges() {
// we will notify listeners upon save()
@@ -100,6 +109,10 @@ public class FileBasedConfig extends StoredConfig {
return configFile;
}
+ boolean exists() {
+ return exists.get();
+ }
+
/**
* {@inheritDoc}
* <p>
@@ -110,49 +123,51 @@ public class FileBasedConfig extends StoredConfig {
*/
@Override
public void load() throws IOException, ConfigInvalidException {
+ lock.readLock().lock();
try {
- FileSnapshot[] lastSnapshot = { null };
- Boolean wasRead = FileUtils.readWithRetries(getFile(), f -> {
- final FileSnapshot oldSnapshot = snapshot;
- final FileSnapshot newSnapshot;
- // don't use config in this snapshot to avoid endless recursion
- newSnapshot = FileSnapshot.saveNoConfig(f);
- lastSnapshot[0] = newSnapshot;
- final byte[] in = IO.readFully(f);
- final ObjectId newHash = hash(in);
- if (hash.equals(newHash)) {
- if (oldSnapshot.equals(newSnapshot)) {
- oldSnapshot.setClean(newSnapshot);
- } else {
- snapshot = newSnapshot;
- }
- } else {
- final String decoded;
- if (isUtf8(in)) {
- decoded = RawParseUtils.decode(UTF_8,
- in, 3, in.length);
- utf8Bom = true;
- } else {
- decoded = RawParseUtils.decode(in);
- }
- fromText(decoded);
- snapshot = newSnapshot;
- hash = newHash;
- }
- return Boolean.TRUE;
- });
+ Boolean wasRead = FileUtils.readWithRetries(getFile(), this::load);
if (wasRead == null) {
clear();
- snapshot = lastSnapshot[0];
+ snapshot = FileSnapshot.MISSING_FILE;
}
+ exists.set(wasRead != null);
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new ConfigInvalidException(MessageFormat
.format(JGitText.get().cannotReadFile, getFile()), e);
+ } finally {
+ lock.readLock().unlock();
}
}
+ private Boolean load(File f) throws Exception {
+ FileSnapshot oldSnapshot = snapshot;
+ // don't use config in this snapshot to avoid endless recursion
+ FileSnapshot newSnapshot = FileSnapshot.saveNoConfig(f);
+ byte[] in = IO.readFully(f);
+ ObjectId newHash = hash(in);
+ if (hash.equals(newHash)) {
+ if (oldSnapshot.equals(newSnapshot)) {
+ oldSnapshot.setClean(newSnapshot);
+ } else {
+ snapshot = newSnapshot;
+ }
+ } else {
+ String decoded;
+ if (isUtf8(in)) {
+ decoded = RawParseUtils.decode(UTF_8, in, 3, in.length);
+ utf8Bom = true;
+ } else {
+ decoded = RawParseUtils.decode(in);
+ }
+ fromText(decoded);
+ snapshot = newSnapshot;
+ hash = newHash;
+ }
+ return Boolean.TRUE;
+ }
+
/**
* {@inheritDoc}
* <p>
@@ -166,38 +181,45 @@ public class FileBasedConfig extends StoredConfig {
*/
@Override
public void save() throws IOException {
- final byte[] out;
- final String text = toText();
- if (utf8Bom) {
- final ByteArrayOutputStream bos = new ByteArrayOutputStream();
- bos.write(0xEF);
- bos.write(0xBB);
- bos.write(0xBF);
- bos.write(text.getBytes(UTF_8));
- out = bos.toByteArray();
- } else {
- out = Constants.encode(text);
- }
-
- final LockFile lf = new LockFile(getFile());
+ lock.writeLock().lock();
try {
- if (!lf.lock()) {
- throw new LockFailedException(getFile());
+ byte[] out;
+ String text = toText();
+ if (utf8Bom) {
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ bos.write(0xEF);
+ bos.write(0xBB);
+ bos.write(0xBF);
+ bos.write(text.getBytes(UTF_8));
+ out = bos.toByteArray();
+ } else {
+ out = Constants.encode(text);
}
- lf.setNeedSnapshotNoConfig(true);
- lf.write(out);
- if (!lf.commit())
- throw new IOException(MessageFormat.format(JGitText.get().cannotCommitWriteTo, getFile()));
+
+ LockFile lf = new LockFile(getFile());
+ try {
+ if (!lf.lock()) {
+ throw new LockFailedException(getFile());
+ }
+ lf.setNeedSnapshotNoConfig(true);
+ lf.write(out);
+ if (!lf.commit()) {
+ throw new IOException(MessageFormat.format(
+ JGitText.get().cannotCommitWriteTo, getFile()));
+ }
+ } finally {
+ lf.unlock();
+ }
+ snapshot = lf.getCommitSnapshot();
+ hash = hash(out);
+ exists.set(true);
} finally {
- lf.unlock();
+ lock.writeLock().unlock();
}
- snapshot = lf.getCommitSnapshot();
- hash = hash(out);
// notify the listeners
fireConfigChangedEvent();
}
- /** {@inheritDoc} */
@Override
public void clear() {
hash = hash(new byte[0]);
@@ -208,7 +230,6 @@ public class FileBasedConfig extends StoredConfig {
return ObjectId.fromRaw(Constants.newMessageDigest().digest(rawText));
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
@@ -246,6 +267,8 @@ public class FileBasedConfig extends StoredConfig {
try {
return IO.readFully(file);
+ } catch (FileNotFoundException e) {
+ return null;
} catch (IOException ioe) {
throw new ConfigInvalidException(MessageFormat
.format(JGitText.get().cannotReadFile, relPath), ioe);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UserConfigFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UserConfigFile.java
new file mode 100644
index 0000000000..c1eaac7b55
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/UserConfigFile.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2023, Thomas Wolf <twolf@apache.org> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.storage.file;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.util.FS;
+
+/**
+ * User (global) git config based on two possible locations,
+ * {@code ~/.gitconfig} and {@code $XDG_CONFIG_HOME/git/config}.
+ * <p>
+ * For reading, both locations are considered, first the XDG file, then the file
+ * in the home directory. All updates occur in the last file read that exists,
+ * or in the home directory file if neither exists. In other words: if only the
+ * XDG file exists, it is updated, otherwise the home directory file is updated.
+ * </p>
+ *
+ * @since 6.7
+ */
+public class UserConfigFile extends FileBasedConfig {
+
+ private final FileBasedConfig parent;
+
+ /**
+ * Creates a new {@link UserConfigFile}.
+ *
+ * @param parent
+ * parent {@link Config}; may be {@code null}
+ * @param config
+ * {@link File} for {@code ~/.gitconfig}
+ * @param xdgConfig
+ * {@link File} for {@code $XDG_CONFIG_HOME/.gitconfig}
+ * @param fileSystem
+ * {@link FS} to use for the two files; normally
+ * {@link FS#DETECTED}
+ */
+ public UserConfigFile(Config parent, @NonNull File config,
+ @NonNull File xdgConfig, @NonNull FS fileSystem) {
+ super(new FileBasedConfig(parent, xdgConfig, fileSystem), config,
+ fileSystem);
+ this.parent = (FileBasedConfig) getBaseConfig();
+ }
+
+ @Override
+ public void setStringList(String section, String subsection, String name,
+ List<String> values) {
+ if (exists() || !parent.exists()) {
+ super.setStringList(section, subsection, name, values);
+ } else {
+ parent.setStringList(section, subsection, name, values);
+ }
+ }
+
+ @Override
+ public void unset(String section, String subsection, String name) {
+ if (exists() || !parent.exists()) {
+ super.unset(section, subsection, name);
+ } else {
+ parent.unset(section, subsection, name);
+ }
+ }
+
+ @Override
+ public void unsetSection(String section, String subsection) {
+ if (exists() || !parent.exists()) {
+ super.unsetSection(section, subsection);
+ } else {
+ parent.unsetSection(section, subsection);
+ }
+ }
+
+ @Override
+ public boolean removeSection(String section, String subsection) {
+ if (exists() || !parent.exists()) {
+ return super.removeSection(section, subsection);
+ }
+ return parent.removeSection(section, subsection);
+ }
+
+ @Override
+ public boolean isOutdated() {
+ return super.isOutdated() || parent.isOutdated();
+ }
+
+ @Override
+ public void load() throws IOException, ConfigInvalidException {
+ if (super.isOutdated()) {
+ super.load();
+ }
+ if (parent.isOutdated()) {
+ parent.load();
+ }
+ }
+
+ @Override
+ public void save() throws IOException {
+ if (exists() || !parent.exists()) {
+ if (exists() || !toText().strip().isEmpty()) {
+ super.save();
+ }
+ } else {
+ parent.save();
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java
index a12f652598..571248eac6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheConfig.java
@@ -16,8 +16,9 @@ import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PACKED_GIT_LIMIT;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PACKED_GIT_MMAP;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PACKED_GIT_OPENFILES;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PACKED_GIT_WINDOWSIZE;
-import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_FILE_TRESHOLD;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_FILE_THRESHOLD;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PACKED_GIT_USE_STRONGREFS;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PACKED_INDEX_GIT_USE_STRONGREFS;
import org.eclipse.jgit.internal.storage.file.WindowCache;
import org.eclipse.jgit.lib.Config;
@@ -39,6 +40,8 @@ public class WindowCacheConfig {
private boolean useStrongRefs;
+ private boolean useStrongIndexRefs;
+
private int packedGitWindowSize;
private boolean packedGitMMAP;
@@ -56,6 +59,7 @@ public class WindowCacheConfig {
packedGitOpenFiles = 128;
packedGitLimit = 10 * MB;
useStrongRefs = false;
+ useStrongIndexRefs = true;
packedGitWindowSize = 8 * KB;
packedGitMMAP = false;
deltaBaseCacheLimit = 10 * MB;
@@ -133,6 +137,31 @@ public class WindowCacheConfig {
}
/**
+ * Get whether the Pack indices cache should use strong references or
+ * SoftReferences
+ *
+ * @return {@code true} if the cached Pack indices should use strong references,
+ * otherwise it will use {@link java.lang.ref.SoftReference}s
+ * @since 6.7
+ */
+ public boolean isPackedIndexGitUseStrongRefs() {
+ return useStrongIndexRefs;
+ }
+
+ /**
+ * Set if the Pack indices cache should use strong refs or soft refs
+ *
+ * @param useStrongRefs
+ * if @{code true} the Pack strongly references cached indices
+ * otherwise it uses {@link java.lang.ref.SoftReference}s which
+ * can be evicted by the Java gc if heap is almost full
+ * @since 6.7
+ */
+ public void setPackedIndexGitUseStrongRefs(boolean useStrongRefs) {
+ this.useStrongIndexRefs = useStrongRefs;
+ }
+
+ /**
* Get size in bytes of a single window mapped or read in from the pack
* file.
*
@@ -270,6 +299,8 @@ public class WindowCacheConfig {
setPackedGitUseStrongRefs(rc.getBoolean(CONFIG_CORE_SECTION,
CONFIG_KEY_PACKED_GIT_USE_STRONGREFS,
isPackedGitUseStrongRefs()));
+ setPackedIndexGitUseStrongRefs(rc.getBoolean(CONFIG_CORE_SECTION,
+ CONFIG_KEY_PACKED_INDEX_GIT_USE_STRONGREFS, isPackedIndexGitUseStrongRefs()));
setPackedGitOpenFiles(rc.getInt(CONFIG_CORE_SECTION, null,
CONFIG_KEY_PACKED_GIT_OPENFILES, getPackedGitOpenFiles()));
setPackedGitLimit(rc.getLong(CONFIG_CORE_SECTION, null,
@@ -283,7 +314,7 @@ public class WindowCacheConfig {
long maxMem = Runtime.getRuntime().maxMemory();
long sft = rc.getLong(CONFIG_CORE_SECTION, null,
- CONFIG_KEY_STREAM_FILE_TRESHOLD, getStreamFileThreshold());
+ CONFIG_KEY_STREAM_FILE_THRESHOLD, getStreamFileThreshold());
sft = Math.min(sft, maxMem / 4); // don't use more than 1/4 of the heap
sft = Math.min(sft, Integer.MAX_VALUE); // cannot exceed array length
setStreamFileThreshold((int) sft);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheStats.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheStats.java
index ae329b686a..668b92cf53 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheStats.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/WindowCacheStats.java
@@ -22,25 +22,10 @@ import org.eclipse.jgit.internal.storage.file.WindowCache;
*/
@MXBean
public interface WindowCacheStats {
- /**
- * @return the number of open files.
- * @deprecated use {@link #getOpenFileCount()} instead
- */
- @Deprecated
- public static int getOpenFiles() {
- return (int) WindowCache.getInstance().getStats().getOpenFileCount();
- }
-
- /**
- * @return the number of open bytes.
- * @deprecated use {@link #getOpenByteCount()} instead
- */
- @Deprecated
- public static long getOpenBytes() {
- return WindowCache.getInstance().getStats().getOpenByteCount();
- }
/**
+ * Get cache statistics
+ *
* @return cache statistics for the WindowCache
* @since 5.1.13
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
index a0c978f6ea..863b79466a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java
@@ -16,6 +16,7 @@ import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BIGFILE_THRESHOLD;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_CONTIGUOUS_COMMIT_COUNT;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_DISTANT_COMMIT_SPAN;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_EXCESSIVE_BRANCH_COUNT;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_EXCESSIVE_BRANCH_TIP_COUNT;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_EXCLUDED_REFS_PREFIXES;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_INACTIVE_BRANCH_AGE_INDAYS;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BITMAP_RECENT_COMMIT_COUNT;
@@ -27,7 +28,11 @@ import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_DELTA_CACHE_SIZE;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_DELTA_COMPRESSION;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_DEPTH;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_INDEXVERSION;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_MIN_BYTES_OBJ_SIZE_INDEX;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_MIN_SIZE_PREVENT_RACYPACK;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PACK_KEPT_OBJECTS;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PRESERVE_OLD_PACKS;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PRUNE_PRESERVED;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_REUSE_DELTAS;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_REUSE_OBJECTS;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_SEARCH_FOR_REUSE_TIMEOUT;
@@ -36,17 +41,16 @@ import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_THREADS;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WAIT_PREVENT_RACYPACK;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WINDOW;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WINDOW_MEMORY;
-import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_MIN_BYTES_OBJ_SIZE_INDEX;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WRITE_REVERSE_INDEX;
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_PACK_SECTION;
-import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PRESERVE_OLD_PACKS;
-import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_PRUNE_PRESERVED;
+import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_REPACK_SECTION;
import java.time.Duration;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.zip.Deflater;
-import org.eclipse.jgit.internal.storage.file.PackIndexWriter;
+import org.eclipse.jgit.internal.storage.file.BasePackIndexWriter;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
@@ -162,6 +166,14 @@ public class PackConfig {
public static final int DEFAULT_INDEX_VERSION = 2;
/**
+ * Default value of the write reverse index option: {@value}
+ *
+ * @see #setWriteReverseIndex(boolean)
+ * @since 6.6
+ */
+ public static final boolean DEFAULT_WRITE_REVERSE_INDEX = false;
+
+ /**
* Default value of the build bitmaps option: {@value}
*
* @see #setBuildBitmaps(boolean)
@@ -169,6 +181,16 @@ public class PackConfig {
*/
public static final boolean DEFAULT_BUILD_BITMAPS = true;
+
+ /**
+ * Default value for including objects in packs locked by .keep file when
+ * repacking: {@value}
+ *
+ * @see #setPackKeptObjects(boolean)
+ * @since 5.13.3
+ */
+ public static final boolean DEFAULT_PACK_KEPT_OBJECTS = false;
+
/**
* Default count of most recent commits to select for bitmaps. Only applies
* when bitmaps are enabled: {@value}
@@ -219,6 +241,17 @@ public class PackConfig {
public static final int DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT = 100;
/**
+ * Default maxium count of branches to create tip bitmaps for. If the number
+ * of branches exceeds this, then tip bitmaps will only be created for the
+ * most recently active branches. Branches exceeding this count will receive
+ * 0 bitmaps: {@value #DEFAULT_BITMAP_EXCESSIVE_BRANCH_TIP_COUNT}
+ *
+ * @see #setBitmapExcessiveBranchTipCount(int)
+ * @since 6.9
+ */
+ public static final int DEFAULT_BITMAP_EXCESSIVE_BRANCH_TIP_COUNT = Integer.MAX_VALUE;
+
+ /**
* Default age at which a branch is considered inactive. Age is taken as the
* number of days ago that the most recent commit was made to a branch. Only
* affects bitmap processing if bitmaps are enabled and the
@@ -247,8 +280,9 @@ public class PackConfig {
public static final int DEFAULT_MIN_BYTES_FOR_OBJ_SIZE_INDEX = -1;
/**
- * Default max time to spend during the search for reuse phase. This
- * optimization is disabled by default: {@value}
+ * Default max time to spend during the search for reuse phase.
+ *
+ * This optimization is disabled by default: {@link Integer#MAX_VALUE} seconds.
*
* @see #setSearchForReuseTimeout(Duration)
* @since 5.13
@@ -292,8 +326,12 @@ public class PackConfig {
private int indexVersion = DEFAULT_INDEX_VERSION;
+ private boolean writeReverseIndex = DEFAULT_WRITE_REVERSE_INDEX;
+
private boolean buildBitmaps = DEFAULT_BUILD_BITMAPS;
+ private boolean packKeptObjects = DEFAULT_PACK_KEPT_OBJECTS;
+
private int bitmapContiguousCommitCount = DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT;
private int bitmapRecentCommitCount = DEFAULT_BITMAP_RECENT_COMMIT_COUNT;
@@ -304,6 +342,8 @@ public class PackConfig {
private int bitmapExcessiveBranchCount = DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT;
+ private int bitmapExcessiveBranchTipCount = DEFAULT_BITMAP_EXCESSIVE_BRANCH_TIP_COUNT;
+
private int bitmapInactiveBranchAgeInDays = DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS;
private String[] bitmapExcludedRefsPrefixes = DEFAULT_BITMAP_EXCLUDED_REFS_PREFIXES;
@@ -373,7 +413,9 @@ public class PackConfig {
this.threads = cfg.threads;
this.executor = cfg.executor;
this.indexVersion = cfg.indexVersion;
+ this.writeReverseIndex = cfg.writeReverseIndex;
this.buildBitmaps = cfg.buildBitmaps;
+ this.packKeptObjects = cfg.packKeptObjects;
this.bitmapContiguousCommitCount = cfg.bitmapContiguousCommitCount;
this.bitmapRecentCommitCount = cfg.bitmapRecentCommitCount;
this.bitmapRecentCommitSpan = cfg.bitmapRecentCommitSpan;
@@ -953,7 +995,7 @@ public class PackConfig {
*
* @return the index version, the special version 0 designates the oldest
* (most compatible) format available for the objects.
- * @see PackIndexWriter
+ * @see BasePackIndexWriter
*/
public int getIndexVersion() {
return indexVersion;
@@ -967,13 +1009,38 @@ public class PackConfig {
* @param version
* the version to write. The special version 0 designates the
* oldest (most compatible) format available for the objects.
- * @see PackIndexWriter
+ * @see BasePackIndexWriter
*/
public void setIndexVersion(int version) {
indexVersion = version;
}
/**
+ * True if the writer should write reverse index files.
+ *
+ * Default setting: {@value #DEFAULT_WRITE_REVERSE_INDEX}
+ *
+ * @return whether the writer should write reverse index files
+ * @since 6.6
+ */
+ public boolean isWriteReverseIndex() {
+ return writeReverseIndex;
+ }
+
+ /**
+ * Set whether the writer will write reverse index files.
+ *
+ * Default setting: {@value #DEFAULT_WRITE_REVERSE_INDEX}
+ *
+ * @param writeReverseIndex
+ * whether the writer should write reverse index files
+ * @since 6.6
+ */
+ public void setWriteReverseIndex(boolean writeReverseIndex) {
+ this.writeReverseIndex = writeReverseIndex;
+ }
+
+ /**
* True if writer is allowed to build bitmaps for indexes.
*
* Default setting: {@value #DEFAULT_BUILD_BITMAPS}
@@ -1003,6 +1070,34 @@ public class PackConfig {
}
/**
+ * Set whether to include objects in `.keep` files when repacking.
+ *
+ * <p>
+ * Default setting: {@value #DEFAULT_PACK_KEPT_OBJECTS}
+ *
+ * @param packKeptObjects
+ * boolean indicating whether to include objects in `.keep` files
+ * when repacking.
+ * @since 5.13.3
+ */
+ public void setPackKeptObjects(boolean packKeptObjects) {
+ this.packKeptObjects = packKeptObjects;
+ }
+
+ /**
+ * True if objects in `.keep` files should be included when repacking.
+ *
+ * Default setting: {@value #DEFAULT_PACK_KEPT_OBJECTS}
+ *
+ * @return True if objects in `.keep` files should be included when
+ * repacking.
+ * @since 5.13.3
+ */
+ public boolean isPackKeptObjects() {
+ return packKeptObjects;
+ }
+
+ /**
* Get the count of most recent commits for which to build bitmaps.
*
* Default setting: {@value #DEFAULT_BITMAP_CONTIGUOUS_COMMIT_COUNT}
@@ -1109,7 +1204,8 @@ public class PackConfig {
* a repository exceeds this number and bitmaps are enabled, "inactive"
* branches will have fewer bitmaps than "active" branches.
*
- * Default setting: {@value #DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT}
+ * Default setting: {@value #DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT}. See
+ * also {@link #getBitmapExcessiveBranchTipCount}.
*
* @return the count of branches deemed "excessive"
* @since 4.2
@@ -1123,7 +1219,8 @@ public class PackConfig {
* a repository exceeds this number and bitmaps are enabled, "inactive"
* branches will have fewer bitmaps than "active" branches.
*
- * Default setting: {@value #DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT}
+ * Default setting: {@value #DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT}. See
+ * also {@link #setBitmapExcessiveBranchTipCount(int)}.
*
* @param count
* the count of branches deemed "excessive"
@@ -1134,6 +1231,57 @@ public class PackConfig {
}
/**
+ * Get the count of branches deemed "excessive". If the count of branches in
+ * a repository exceeds this number and bitmaps are enabled, branches
+ * exceeding this count will have no bitmaps selected. Branches are indexed
+ * most recent first.
+ *
+ * <li>The first {@code DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT} most active
+ * branches have full bitmap coverage.
+ * <li>The {@code DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT} to {@code
+ * DEFAULT_BITMAP_EXCESSIVE_BRANCH_TIP_COUNT} most active branches have
+ * only the tip commit covered.
+ * <li>The remaining branches have no bitmap coverage.
+ *
+ * If {@link #getBitmapExcessiveBranchCount()} is greater, then that value
+ * will override this value.
+ *
+ * Default setting: {@value #DEFAULT_BITMAP_EXCESSIVE_BRANCH_TIP_COUNT}
+ *
+ * @return the count of branch tips deemed "excessive"
+ * @since 6.9
+ */
+ public int getBitmapExcessiveBranchTipCount() {
+ return bitmapExcessiveBranchTipCount;
+ }
+
+ /**
+ * Get the count of branches deemed "excessive". If the count of branches in
+ * a repository exceeds this number and bitmaps are enabled, branches
+ * exceeding this count will have no bitmaps selected. Branches are indexed
+ * most recent first.
+ *
+ * <li>The first {@code DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT} most active
+ * branches have full bitmap coverage.
+ * <li>The {@code DEFAULT_BITMAP_EXCESSIVE_BRANCH_COUNT} to {@code
+ * DEFAULT_BITMAP_EXCESSIVE_BRANCH_TIP_COUNT} most active branches have
+ * only the tip commit covered.
+ * <li>The remaining branches have no bitmap coverage.
+ *
+ * If {@link #getBitmapExcessiveBranchCount()} is greater, then that value
+ * will override this value.
+ *
+ * Default setting: {@value #DEFAULT_BITMAP_EXCESSIVE_BRANCH_TIP_COUNT}
+ *
+ * @param count
+ * the count of branch tips deemed "excessive"
+ * @since 6.9
+ */
+ public void setBitmapExcessiveBranchTipCount(int count) {
+ bitmapExcessiveBranchTipCount = count;
+ }
+
+ /**
* Get the age in days that marks a branch as "inactive".
*
* Default setting: {@value #DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS}
@@ -1148,7 +1296,7 @@ public class PackConfig {
/**
* Get the max time to spend during the search for reuse phase.
*
- * Default setting: {@value #DEFAULT_SEARCH_FOR_REUSE_TIMEOUT}
+ * Default setting: {@link #DEFAULT_SEARCH_FOR_REUSE_TIMEOUT}
*
* @return the maximum time to spend during the search for reuse phase.
* @since 5.13
@@ -1160,7 +1308,7 @@ public class PackConfig {
/**
* Set the age in days that marks a branch as "inactive".
*
- * Default setting: {@value #DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS}
+ * Default setting: {@link #DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS}
*
* @param ageInDays
* the age in days that marks a branch as "inactive"
@@ -1197,7 +1345,7 @@ public class PackConfig {
* @param timeout
* max time allowed during the search for reuse phase
*
- * Default setting: {@value #DEFAULT_SEARCH_FOR_REUSE_TIMEOUT}
+ * Default setting: {@link #DEFAULT_SEARCH_FOR_REUSE_TIMEOUT}
* @since 5.13
*/
public void setSearchForReuseTimeout(Duration timeout) {
@@ -1286,8 +1434,14 @@ public class PackConfig {
setSinglePack(rc.getBoolean(CONFIG_PACK_SECTION,
CONFIG_KEY_SINGLE_PACK,
getSinglePack()));
- setBuildBitmaps(rc.getBoolean(CONFIG_PACK_SECTION,
- CONFIG_KEY_BUILD_BITMAPS, isBuildBitmaps()));
+ setWriteReverseIndex(rc.getBoolean(CONFIG_PACK_SECTION,
+ CONFIG_KEY_WRITE_REVERSE_INDEX, isWriteReverseIndex()));
+ boolean buildBitmapsFromConfig = rc.getBoolean(CONFIG_PACK_SECTION,
+ CONFIG_KEY_BUILD_BITMAPS, isBuildBitmaps());
+ setBuildBitmaps(buildBitmapsFromConfig);
+ setPackKeptObjects(rc.getBoolean(CONFIG_REPACK_SECTION,
+ CONFIG_KEY_PACK_KEPT_OBJECTS,
+ buildBitmapsFromConfig || isPackKeptObjects()));
setBitmapContiguousCommitCount(rc.getInt(CONFIG_PACK_SECTION,
CONFIG_KEY_BITMAP_CONTIGUOUS_COMMIT_COUNT,
getBitmapContiguousCommitCount()));
@@ -1303,6 +1457,9 @@ public class PackConfig {
setBitmapExcessiveBranchCount(rc.getInt(CONFIG_PACK_SECTION,
CONFIG_KEY_BITMAP_EXCESSIVE_BRANCH_COUNT,
getBitmapExcessiveBranchCount()));
+ setBitmapExcessiveBranchTipCount(rc.getInt(CONFIG_PACK_SECTION,
+ CONFIG_KEY_BITMAP_EXCESSIVE_BRANCH_TIP_COUNT,
+ getBitmapExcessiveBranchTipCount()));
setBitmapInactiveBranchAgeInDays(rc.getInt(CONFIG_PACK_SECTION,
CONFIG_KEY_BITMAP_INACTIVE_BRANCH_AGE_INDAYS,
getBitmapInactiveBranchAgeInDays()));
@@ -1330,7 +1487,6 @@ public class PackConfig {
CONFIG_KEY_PRUNE_PRESERVED, DEFAULT_PRUNE_PRESERVED));
}
- /** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder b = new StringBuilder();
@@ -1347,6 +1503,7 @@ public class PackConfig {
b.append(", reuseDeltas=").append(isReuseDeltas()); //$NON-NLS-1$
b.append(", reuseObjects=").append(isReuseObjects()); //$NON-NLS-1$
b.append(", deltaCompress=").append(isDeltaCompress()); //$NON-NLS-1$
+ b.append(", writeReverseIndex=").append(isWriteReverseIndex()); //$NON-NLS-1$
b.append(", buildBitmaps=").append(isBuildBitmaps()); //$NON-NLS-1$
b.append(", bitmapContiguousCommitCount=") //$NON-NLS-1$
.append(getBitmapContiguousCommitCount());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java
index 4d18337fae..64a1eb2e1a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java
@@ -111,6 +111,8 @@ public class PackStatistics {
}
/**
+ * Get total number of objects output
+ *
* @return total number of objects output. This total includes the value
* of {@link #getDeltas()}.
*/
@@ -119,6 +121,8 @@ public class PackStatistics {
}
/**
+ * Get total number of deltas output
+ *
* @return total number of deltas output. This may be lower than the
* actual number of deltas if a cached pack was reused.
*/
@@ -127,6 +131,9 @@ public class PackStatistics {
}
/**
+ * Get number of objects whose existing representation was reused in the
+ * output
+ *
* @return number of objects whose existing representation was reused in
* the output. This count includes {@link #getReusedDeltas()}.
*/
@@ -135,6 +142,9 @@ public class PackStatistics {
}
/**
+ * Get number of deltas whose existing representation was reused in the
+ * output
+ *
* @return number of deltas whose existing representation was reused in
* the output, as their base object was also output or was
* assumed present for a thin pack. This may be lower than the
@@ -145,6 +155,8 @@ public class PackStatistics {
}
/**
+ * Get total number of bytes written
+ *
* @return total number of bytes written. This size includes the object
* headers as well as the compressed data. This size also
* includes all of {@link #getDeltaBytes()}.
@@ -154,6 +166,8 @@ public class PackStatistics {
}
/**
+ * Get number of delta bytes written
+ *
* @return number of delta bytes written. This size includes the object
* headers for the delta objects.
*/
@@ -641,6 +655,8 @@ public class PackStatistics {
}
/**
+ * Get number of trees traversed
+ *
* @return number of trees traversed in the walk when writing the pack.
* @since 5.4
*/
@@ -649,6 +665,8 @@ public class PackStatistics {
}
/**
+ * Get amount of packfiles offloaded
+ *
* @return amount of packfiles offloaded (sent as "packfile-uri")/
* @since 5.6
*/
@@ -657,6 +675,8 @@ public class PackStatistics {
}
/**
+ * Get total size (in bytes) offloaded
+ *
* @return total size (in bytes) offloaded to HTTP downloads.
* @since 5.6
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleConflict.java b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleConflict.java
index 856eb725dd..e324ab1afc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleConflict.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleConflict.java
@@ -55,9 +55,11 @@ public class SubmoduleConflict extends Sequence {
private final ObjectId objectId;
/**
- * Create a SubmoduleConflict for the given submodule object id
- * @param objectId
- */
+ * Create a SubmoduleConflict for the given submodule object id
+ *
+ * @param objectId
+ * the id of the object to create a submodule conflict for
+ */
public SubmoduleConflict(ObjectId objectId) {
super();
this.objectId = objectId;
@@ -69,8 +71,10 @@ public class SubmoduleConflict extends Sequence {
}
/**
- * @return the object id for the conflicting submodule
- */
+ * Get objectId for the conflicting submodule
+ *
+ * @return the object id for the conflicting submodule
+ */
public ObjectId getObjectId() {
return objectId;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
index bf77021b0c..105cba7d28 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
@@ -87,6 +87,7 @@ public class SubmoduleWalk implements AutoCloseable {
* @return generator over submodule index entries. The caller is responsible
* for calling {@link #close()}.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public static SubmoduleWalk forIndex(Repository repository)
throws IOException {
@@ -116,6 +117,7 @@ public class SubmoduleWalk implements AutoCloseable {
* @return generator at given path. The caller is responsible for calling
* {@link #close()}. Null if no submodule at given path.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public static SubmoduleWalk forPath(Repository repository,
AnyObjectId treeId, String path) throws IOException {
@@ -150,6 +152,7 @@ public class SubmoduleWalk implements AutoCloseable {
* @return generator at given path. The caller is responsible for calling
* {@link #close()}. Null if no submodule at given path.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public static SubmoduleWalk forPath(Repository repository,
AbstractTreeIterator iterator, String path) throws IOException {
@@ -193,6 +196,7 @@ public class SubmoduleWalk implements AutoCloseable {
* submodule path
* @return repository or null if repository doesn't exist
* @throws java.io.IOException
+ * if an IO error occurred
*/
public static Repository getSubmoduleRepository(final Repository parent,
final String path) throws IOException {
@@ -209,6 +213,7 @@ public class SubmoduleWalk implements AutoCloseable {
* submodule path
* @return repository or null if repository doesn't exist
* @throws java.io.IOException
+ * if an IO error occurred
*/
public static Repository getSubmoduleRepository(final File parent,
final String path) throws IOException {
@@ -220,11 +225,14 @@ public class SubmoduleWalk implements AutoCloseable {
* abstraction
*
* @param parent
+ * {@link Repository} that contains the submodule
* @param path
+ * of the working tree of the submodule
* @param fs
* the file system abstraction to be used
* @return repository or null if repository doesn't exist
* @throws IOException
+ * if an IO error occurred
* @since 4.10
*/
public static Repository getSubmoduleRepository(final File parent,
@@ -249,7 +257,7 @@ public class SubmoduleWalk implements AutoCloseable {
* @return the {@link Repository} of the submodule, or {@code null} if it
* doesn't exist
* @throws IOException
- * on errors
+ * if an IO error occurred
* @since 5.6
*/
public static Repository getSubmoduleRepository(File parent, String path,
@@ -286,6 +294,7 @@ public class SubmoduleWalk implements AutoCloseable {
* absolute or relative URL of the submodule repository
* @return resolved URL
* @throws java.io.IOException
+ * if an IO error occurred
*/
public static String getSubmoduleRemoteUrl(final Repository parent,
final String url) throws IOException {
@@ -369,6 +378,7 @@ public class SubmoduleWalk implements AutoCloseable {
* @param repository
* the {@link org.eclipse.jgit.lib.Repository}.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public SubmoduleWalk(Repository repository) throws IOException {
this.repository = repository;
@@ -419,13 +429,14 @@ public class SubmoduleWalk implements AutoCloseable {
* The root tree is not read until the first submodule is encountered by the
* walk.
* <p>
- * This method need only be called if constructing a walk manually instead of
- * with one of the static factory methods above.
+ * This method need only be called if constructing a walk manually instead
+ * of with one of the static factory methods above.
*
* @param id
* ID of a tree containing .gitmodules
* @return this generator
* @throws java.io.IOException
+ * if an IO error occurred
*/
public SubmoduleWalk setRootTree(AnyObjectId id) throws IOException {
final CanonicalTreeParser p = new CanonicalTreeParser();
@@ -448,6 +459,7 @@ public class SubmoduleWalk implements AutoCloseable {
* @throws java.io.IOException
* if an error occurred, or if the repository is bare
* @throws org.eclipse.jgit.errors.ConfigInvalidException
+ * if .gitmodules config is invalid
*/
public SubmoduleWalk loadModulesConfig() throws IOException, ConfigInvalidException {
if (rootTree == null) {
@@ -512,11 +524,13 @@ public class SubmoduleWalk implements AutoCloseable {
*
* @param repository
* the repository to check
- * @return <code>true</code> if the working tree contains a .gitmodules file,
- * <code>false</code> otherwise. Always returns <code>false</code>
- * for bare repositories.
+ * @return <code>true</code> if the working tree contains a .gitmodules
+ * file, <code>false</code> otherwise. Always returns
+ * <code>false</code> for bare repositories.
* @throws java.io.IOException
- * @throws CorruptObjectException if any.
+ * if an IO error occurred
+ * @throws CorruptObjectException
+ * if a corrupt object was found
* @since 3.6
*/
public static boolean containsGitModulesFile(Repository repository)
@@ -526,7 +540,7 @@ public class SubmoduleWalk implements AutoCloseable {
}
File modulesFile = new File(repository.getWorkTree(),
Constants.DOT_GIT_MODULES);
- return (modulesFile.exists());
+ return modulesFile.exists();
}
private void lazyLoadModulesConfig() throws IOException, ConfigInvalidException {
@@ -560,6 +574,7 @@ public class SubmoduleWalk implements AutoCloseable {
* object.
* @return this generator
* @throws org.eclipse.jgit.errors.CorruptObjectException
+ * if a corrupt object was found
*/
public SubmoduleWalk setTree(AbstractTreeIterator iterator)
throws CorruptObjectException {
@@ -574,10 +589,11 @@ public class SubmoduleWalk implements AutoCloseable {
* an {@link org.eclipse.jgit.lib.AnyObjectId} object.
* @return this generator
* @throws java.io.IOException
+ * if an IO error occurred
* @throws IncorrectObjectTypeException
- * if any.
+ * if object has unexpected type
* @throws MissingObjectException
- * if any.
+ * if object is missing
*/
public SubmoduleWalk setTree(AnyObjectId treeId) throws IOException {
walk.addTree(treeId);
@@ -614,6 +630,7 @@ public class SubmoduleWalk implements AutoCloseable {
*
* @return true if entry found, false otherwise
* @throws java.io.IOException
+ * if an IO error occurred
*/
public boolean next() throws IOException {
while (walk.next()) {
@@ -656,9 +673,11 @@ public class SubmoduleWalk implements AutoCloseable {
* name of .git/config)
*
* @since 4.10
- * @return name
+ * @return name of the submodule
* @throws ConfigInvalidException
+ * if the .gitmodules config is invalid
* @throws IOException
+ * if an IO error occurred
*/
public String getModuleName() throws IOException, ConfigInvalidException {
lazyLoadModulesConfig();
@@ -680,7 +699,9 @@ public class SubmoduleWalk implements AutoCloseable {
*
* @return configured path
* @throws org.eclipse.jgit.errors.ConfigInvalidException
+ * if .gitmodules config is invalid
* @throws java.io.IOException
+ * if an IO error occurred
*/
public String getModulesPath() throws IOException, ConfigInvalidException {
lazyLoadModulesConfig();
@@ -694,7 +715,9 @@ public class SubmoduleWalk implements AutoCloseable {
*
* @return configured URL
* @throws org.eclipse.jgit.errors.ConfigInvalidException
+ * if .gitmodules config is invalid
* @throws java.io.IOException
+ * if an IO error occurred
*/
public String getConfigUrl() throws IOException, ConfigInvalidException {
return repoConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
@@ -707,7 +730,9 @@ public class SubmoduleWalk implements AutoCloseable {
*
* @return configured URL
* @throws org.eclipse.jgit.errors.ConfigInvalidException
+ * if .gitmodules config is invalid
* @throws java.io.IOException
+ * if an IO error occurred
*/
public String getModulesUrl() throws IOException, ConfigInvalidException {
lazyLoadModulesConfig();
@@ -721,7 +746,9 @@ public class SubmoduleWalk implements AutoCloseable {
*
* @return update value
* @throws org.eclipse.jgit.errors.ConfigInvalidException
+ * if .gitmodules config is invalid
* @throws java.io.IOException
+ * if an IO error occurred
*/
public String getConfigUpdate() throws IOException, ConfigInvalidException {
return repoConfig.getString(ConfigConstants.CONFIG_SUBMODULE_SECTION,
@@ -734,7 +761,9 @@ public class SubmoduleWalk implements AutoCloseable {
*
* @return update value
* @throws org.eclipse.jgit.errors.ConfigInvalidException
+ * if .gitmodules config is invalid
* @throws java.io.IOException
+ * if an IO error occurred
*/
public String getModulesUpdate() throws IOException, ConfigInvalidException {
lazyLoadModulesConfig();
@@ -748,7 +777,9 @@ public class SubmoduleWalk implements AutoCloseable {
*
* @return ignore value
* @throws org.eclipse.jgit.errors.ConfigInvalidException
+ * if .gitmodules config is invalid
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.6
*/
public IgnoreSubmoduleMode getModulesIgnore() throws IOException,
@@ -756,14 +787,14 @@ public class SubmoduleWalk implements AutoCloseable {
IgnoreSubmoduleMode mode = repoConfig.getEnum(
IgnoreSubmoduleMode.values(),
ConfigConstants.CONFIG_SUBMODULE_SECTION, getModuleName(),
- ConfigConstants.CONFIG_KEY_IGNORE, null);
+ ConfigConstants.CONFIG_KEY_IGNORE);
if (mode != null) {
return mode;
}
lazyLoadModulesConfig();
- return modulesConfig.getEnum(IgnoreSubmoduleMode.values(),
- ConfigConstants.CONFIG_SUBMODULE_SECTION, getModuleName(),
- ConfigConstants.CONFIG_KEY_IGNORE, IgnoreSubmoduleMode.NONE);
+ return modulesConfig.getEnum(ConfigConstants.CONFIG_SUBMODULE_SECTION,
+ getModuleName(), ConfigConstants.CONFIG_KEY_IGNORE,
+ IgnoreSubmoduleMode.NONE);
}
/**
@@ -771,6 +802,7 @@ public class SubmoduleWalk implements AutoCloseable {
*
* @return repository or null if non-existent
* @throws java.io.IOException
+ * if an IO error occurred
*/
public Repository getRepository() throws IOException {
return getSubmoduleRepository(repository.getWorkTree(), path,
@@ -782,6 +814,7 @@ public class SubmoduleWalk implements AutoCloseable {
*
* @return object id of HEAD reference
* @throws java.io.IOException
+ * if an IO error occurred
*/
public ObjectId getHead() throws IOException {
try (Repository subRepo = getRepository()) {
@@ -797,6 +830,7 @@ public class SubmoduleWalk implements AutoCloseable {
*
* @return ref name, null on failures
* @throws java.io.IOException
+ * if an IO error occurred
*/
public String getHeadRef() throws IOException {
try (Repository subRepo = getRepository()) {
@@ -816,7 +850,9 @@ public class SubmoduleWalk implements AutoCloseable {
*
* @return resolved remote URL
* @throws java.io.IOException
+ * if an IO error occurred
* @throws org.eclipse.jgit.errors.ConfigInvalidException
+ * if .gitmodules config is invalid
*/
public String getRemoteUrl() throws IOException, ConfigInvalidException {
String url = getModulesUrl();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java
index fb9c14576a..260a85e0e4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java
@@ -58,7 +58,6 @@ import org.eclipse.jgit.revwalk.RevWalk;
* @since 2.0
*/
public abstract class AbstractAdvertiseRefsHook implements AdvertiseRefsHook {
- /** {@inheritDoc} */
@Override
public void advertiseRefs(UploadPack uploadPack)
throws ServiceMayNotContinueException {
@@ -67,7 +66,6 @@ public abstract class AbstractAdvertiseRefsHook implements AdvertiseRefsHook {
}
/**
- * {@inheritDoc}
*/
@Override
public void advertiseRefs(ReceivePack receivePack)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java
index 84c36915a2..6381f1e946 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java
@@ -92,6 +92,7 @@ public interface AdvertiseRefsHook {
* @throws org.eclipse.jgit.transport.ServiceMayNotContinueException
* abort; the message will be sent to the user.
* @throws IOException
+ * if an IO error occurred
* @since 5.6
*/
void advertiseRefs(ReceivePack receivePack)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java
index eb9c673ef3..6db0775769 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java
@@ -50,9 +50,6 @@ public class AdvertiseRefsHookChain implements AdvertiseRefsHook {
}
}
- /**
- * {@inheritDoc}
- */
@Override
public void advertiseRefs(ReceivePack rp)
throws IOException {
@@ -60,7 +57,6 @@ public class AdvertiseRefsHookChain implements AdvertiseRefsHook {
hooks[i].advertiseRefs(rp);
}
- /** {@inheritDoc} */
@Override
public void advertiseRefs(UploadPack rp)
throws ServiceMayNotContinueException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
index d482521747..9d9f5495fe 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AmazonS3.java
@@ -596,7 +596,7 @@ public class AmazonS3 {
final String key, final Map<String, String> args)
throws IOException {
final StringBuilder urlstr = new StringBuilder();
- urlstr.append(protocol); //$NON-NLS-1$
+ urlstr.append(protocol);
urlstr.append("://"); //$NON-NLS-1$
urlstr.append(bucket);
urlstr.append('.');
@@ -757,8 +757,19 @@ public class AmazonS3 {
final XMLReader xr;
try {
- xr = SAXParserFactory.newInstance().newSAXParser()
- .getXMLReader();
+ SAXParserFactory saxParserFactory = SAXParserFactory
+ .newInstance();
+ saxParserFactory.setNamespaceAware(true);
+ saxParserFactory.setFeature(
+ "http://xml.org/sax/features/external-general-entities", //$NON-NLS-1$
+ false);
+ saxParserFactory.setFeature(
+ "http://xml.org/sax/features/external-parameter-entities", //$NON-NLS-1$
+ false);
+ saxParserFactory.setFeature(
+ "http://apache.org/xml/features/disallow-doctype-decl", //$NON-NLS-1$
+ true);
+ xr = saxParserFactory.newSAXParser().getXMLReader();
} catch (SAXException | ParserConfigurationException e) {
throw new IOException(
JGitText.get().noXMLParserAvailable, e);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AwsRequestSignerV4.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AwsRequestSignerV4.java
index ab3013762b..cf4420fe58 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AwsRequestSignerV4.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AwsRequestSignerV4.java
@@ -10,6 +10,8 @@
package org.eclipse.jgit.transport;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
@@ -145,7 +147,7 @@ public final class AwsRequestSignerV4 {
canonicalRequest.getBytes(StandardCharsets.UTF_8)));
// compute the signing key
- byte[] secretKey = (SCHEME + new String(awsSecretKey)).getBytes();
+ byte[] secretKey = (SCHEME + new String(awsSecretKey)).getBytes(UTF_8);
byte[] dateKey = signStringWithKey(scopeDate, secretKey);
byte[] regionKey = signStringWithKey(regionName, dateKey);
byte[] serviceKey = signStringWithKey(serviceName, regionKey);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java
index 9e229a18ad..3ac9f59ba6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java
@@ -38,25 +38,21 @@ public abstract class BaseConnection implements Connection {
private Writer messageWriter;
- /** {@inheritDoc} */
@Override
public Map<String, Ref> getRefsMap() {
return advertisedRefs;
}
- /** {@inheritDoc} */
@Override
public final Collection<Ref> getRefs() {
return advertisedRefs.values();
}
- /** {@inheritDoc} */
@Override
public final Ref getRef(String name) {
return advertisedRefs.get(name);
}
- /** {@inheritDoc} */
@Override
public String getMessages() {
return messageWriter != null ? messageWriter.toString() : ""; //$NON-NLS-1$
@@ -84,7 +80,6 @@ public abstract class BaseConnection implements Connection {
peerUserAgent = agent;
}
- /** {@inheritDoc} */
@Override
public abstract void close();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java
index 6954bd9a0e..48992f3e3b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseFetchConnection.java
@@ -31,7 +31,6 @@ import org.eclipse.jgit.lib.Ref;
*/
abstract class BaseFetchConnection extends BaseConnection implements
FetchConnection {
- /** {@inheritDoc} */
@Override
public final void fetch(final ProgressMonitor monitor,
final Collection<Ref> want, final Set<ObjectId> have)
@@ -39,7 +38,6 @@ abstract class BaseFetchConnection extends BaseConnection implements
fetch(monitor, want, have, null);
}
- /** {@inheritDoc} */
@Override
public final void fetch(final ProgressMonitor monitor,
final Collection<Ref> want, final Set<ObjectId> have,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
index 09c559d7b5..bac5025f83 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
@@ -64,6 +64,7 @@ import org.eclipse.jgit.util.io.TimeoutOutputStream;
*/
abstract class BasePackConnection extends BaseConnection {
+ /** The capability prefix for a symlink */
protected static final String CAPABILITY_SYMREF_PREFIX = "symref="; //$NON-NLS-1$
/** The repository this transport fetches into, or pushes out of. */
@@ -72,7 +73,7 @@ abstract class BasePackConnection extends BaseConnection {
/** Remote repository location. */
protected final URIish uri;
- /** A transport connected to {@link #uri}. */
+ /** A transport connected to {@link BasePackConnection#uri}. */
protected final Transport transport;
/** Low-level input stream, if a timeout was configured. */
@@ -81,7 +82,10 @@ abstract class BasePackConnection extends BaseConnection {
/** Low-level output stream, if a timeout was configured. */
protected TimeoutOutputStream timeoutOut;
- /** Timer to manage {@link #timeoutIn} and {@link #timeoutOut}. */
+ /**
+ * Timer to manage {@link #timeoutIn} and
+ * {@link BasePackConnection#timeoutOut}.
+ */
private InterruptTimer myTimer;
/** Input stream reading from the remote. */
@@ -90,13 +94,16 @@ abstract class BasePackConnection extends BaseConnection {
/** Output stream sending to the remote. */
protected OutputStream out;
- /** Packet line decoder around {@link #in}. */
+ /** Packet line decoder around {@link BasePackConnection#in}. */
protected PacketLineIn pckIn;
- /** Packet line encoder around {@link #out}. */
+ /** Packet line encoder around {@link BasePackConnection#out}. */
protected PacketLineOut pckOut;
- /** Send {@link PacketLineOut#end()} before closing {@link #out}? */
+ /**
+ * Send {@link PacketLineOut#end()} before closing
+ * {@link BasePackConnection#out}?
+ */
protected boolean outNeedsEnd;
/** True if this is a stateless RPC connection. */
@@ -486,7 +493,6 @@ abstract class BasePackConnection extends BaseConnection {
* <p>
* If refMap already contains an entry for symRef.key, it is replaced.
* </p>
- * </p>
* <p>
* For example, given:
* </p>
@@ -627,7 +633,6 @@ abstract class BasePackConnection extends BaseConnection {
}
}
- /** {@inheritDoc} */
@Override
public String getPeerUserAgent() {
String agent = remoteCapabilities.get(OPTION_AGENT);
@@ -642,7 +647,6 @@ abstract class BasePackConnection extends BaseConnection {
return new PackProtocolException(uri, MessageFormat.format(JGitText.get().invalidRefAdvertisementLine, line));
}
- /** {@inheritDoc} */
@Override
public void close() {
if (out != 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
index 8909380176..be0d37b96e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
@@ -12,10 +12,10 @@
package org.eclipse.jgit.transport;
-import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DELIM;
import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN;
import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_NOT;
import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_SINCE;
+import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DELIM;
import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DONE;
import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_END;
import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ERR;
@@ -32,10 +32,11 @@ import java.time.Instant;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -334,7 +335,6 @@ public abstract class BasePackFetchConnection extends BasePackConnection
}
}
- /** {@inheritDoc} */
@Override
public final void fetch(final ProgressMonitor monitor,
final Collection<Ref> want, final Set<ObjectId> have)
@@ -342,7 +342,6 @@ public abstract class BasePackFetchConnection extends BasePackConnection
fetch(monitor, want, have, null);
}
- /** {@inheritDoc} */
@Override
public final void fetch(final ProgressMonitor monitor,
final Collection<Ref> want, final Set<ObjectId> have,
@@ -351,25 +350,21 @@ public abstract class BasePackFetchConnection extends BasePackConnection
doFetch(monitor, want, have, outputStream);
}
- /** {@inheritDoc} */
@Override
public boolean didFetchIncludeTags() {
return false;
}
- /** {@inheritDoc} */
@Override
public boolean didFetchTestConnectivity() {
return false;
}
- /** {@inheritDoc} */
@Override
public void setPackLockMessage(String message) {
lockMessage = message;
}
- /** {@inheritDoc} */
@Override
public Collection<PackLock> getPackLocks() {
if (packLock != null)
@@ -406,11 +401,14 @@ public abstract class BasePackFetchConnection extends BasePackConnection
protected void doFetch(final ProgressMonitor monitor,
final Collection<Ref> want, final Set<ObjectId> have,
OutputStream outputStream) throws TransportException {
+ boolean hasObjects = !have.isEmpty();
try {
noProgress = monitor == NullProgressMonitor.INSTANCE;
- markRefsAdvertised();
- markReachable(want, have, maxTimeWanted(want));
+ if (hasObjects) {
+ markRefsAdvertised();
+ }
+ markReachable(want, have, maxTimeWanted(want, hasObjects));
if (TransferConfig.ProtocolVersion.V2
.equals(getProtocolVersion())) {
@@ -422,7 +420,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection
state = new TemporaryBuffer.Heap(Integer.MAX_VALUE);
pckState = new PacketLineOut(state);
try {
- doFetchV2(monitor, want, outputStream);
+ doFetchV2(monitor, want, outputStream, hasObjects);
} finally {
clearState();
}
@@ -434,7 +432,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection
pckState = new PacketLineOut(state);
}
PacketLineOut output = statelessRPC ? pckState : pckOut;
- if (sendWants(want, output)) {
+ if (sendWants(want, output, hasObjects)) {
boolean mayHaveShallow = depth != null || deepenSince != null || !deepenNots.isEmpty();
Set<ObjectId> shallowCommits = local.getObjectDatabase().getShallowCommits();
if (isCapableOf(GitProtocolConstants.CAPABILITY_SHALLOW)) {
@@ -461,7 +459,8 @@ public abstract class BasePackFetchConnection extends BasePackConnection
}
private void doFetchV2(ProgressMonitor monitor, Collection<Ref> want,
- OutputStream outputStream) throws IOException, CancelledException {
+ OutputStream outputStream, boolean hasObjects)
+ throws IOException, CancelledException {
sideband = true;
negotiateBegin();
@@ -483,7 +482,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection
pckState.writeString(capability);
}
- if (!sendWants(want, pckState)) {
+ if (!sendWants(want, pckState, hasObjects)) {
// We already have everything we wanted.
return;
}
@@ -659,7 +658,6 @@ public abstract class BasePackFetchConnection extends BasePackConnection
return gotReady;
}
- /** {@inheritDoc} */
@Override
public void close() {
if (walk != null)
@@ -671,8 +669,12 @@ public abstract class BasePackFetchConnection extends BasePackConnection
return local.getConfig().get(FetchConfig::new);
}
- private int maxTimeWanted(Collection<Ref> wants) {
+ private int maxTimeWanted(Collection<Ref> wants, boolean hasObjects) {
int maxTime = 0;
+ if (!hasObjects) {
+ // we don't have any objects locally, we can immediately bail out
+ return maxTime;
+ }
for (Ref r : wants) {
try {
final RevObject obj = walk.parseAny(r.getObjectId());
@@ -689,36 +691,30 @@ public abstract class BasePackFetchConnection extends BasePackConnection
}
private void markReachable(Collection<Ref> want, Set<ObjectId> have,
- int maxTime)
- throws IOException {
- Set<String> wantRefs = want.stream().map(Ref::getName)
- .collect(Collectors.toSet());
-
- for (Ref r : local.getRefDatabase().getRefs()) {
- if (useNegotiationTip && !wantRefs.contains(r.getName())) {
- continue;
+ int maxTime) throws IOException {
+ Collection<Ref> refsToMark;
+ if (useNegotiationTip) {
+ refsToMark = translateToLocalTips(want);
+ if (refsToMark.size() < want.size()) {
+ refsToMark.addAll(local.getRefDatabase().getRefs());
}
-
- ObjectId id = r.getPeeledObjectId();
- if (id == null)
- id = r.getObjectId();
- if (id == null)
- continue;
- parseReachable(id);
+ } else {
+ refsToMark = local.getRefDatabase().getRefs();
}
+ markReachableRefTips(refsToMark);
for (ObjectId id : local.getAdditionalHaves())
- parseReachable(id);
+ markReachable(id);
for (ObjectId id : have)
- parseReachable(id);
+ markReachable(id);
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);
+ Instant maxWhen = Instant.ofEpochSecond(maxTime);
walk.sort(RevSort.COMMIT_TIME_DESC);
walk.markStart(reachableCommits);
walk.setRevFilter(CommitTimeRevFilter.after(maxWhen));
@@ -738,7 +734,37 @@ public abstract class BasePackFetchConnection extends BasePackConnection
}
}
- private void parseReachable(ObjectId id) {
+ private Collection<Ref> translateToLocalTips(Collection<Ref> want)
+ throws IOException {
+ String[] refs = want.stream().map(Ref::getName)
+ .collect(Collectors.toSet()).toArray(String[]::new);
+ Map<String, Ref> wantRefMap = local.getRefDatabase().exactRef(refs);
+ return wantRefMap.values().stream()
+ .filter(r -> getRefObjectId(r) != null)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Marks commits reachable.
+ *
+ * @param refsToMark
+ * references that client is requesting to be marked.
+ */
+ private void markReachableRefTips(Collection<Ref> refsToMark) {
+ refsToMark.stream().map(BasePackFetchConnection::getRefObjectId)
+ .filter(Objects::nonNull)
+ .forEach(oid -> markReachable(oid));
+ }
+
+ private static ObjectId getRefObjectId(Ref ref) {
+ ObjectId id = ref.getPeeledObjectId();
+ if (id == null) {
+ id = ref.getObjectId();
+ }
+ return id;
+ }
+
+ private void markReachable(ObjectId id) {
try {
RevCommit o = walk.parseCommit(id);
if (!o.has(REACHABLE)) {
@@ -750,7 +776,8 @@ public abstract class BasePackFetchConnection extends BasePackConnection
}
}
- private boolean sendWants(Collection<Ref> want, PacketLineOut p)
+ private boolean sendWants(Collection<Ref> want, PacketLineOut p,
+ boolean hasObjects)
throws IOException {
boolean first = true;
for (Ref r : want) {
@@ -759,7 +786,9 @@ public abstract class BasePackFetchConnection extends BasePackConnection
continue;
}
// if depth is set we need to fetch the objects even if they are already available
- if (transport.getDepth() == null) {
+ if (transport.getDepth() == null
+ // only check reachable objects when we have objects
+ && hasObjects) {
try {
if (walk.parseAny(objectId).has(REACHABLE)) {
// We already have this object. Asking for it is
@@ -845,7 +874,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection
if (statelessRPC && multiAck != MultiAck.DETAILED) {
// Our stateless RPC implementation relies upon the detailed
// ACK status to tell us common objects for reuse in future
- // requests. If its not enabled, we can't talk to the peer.
+ // requests. If its not enabled, we can't talk to the peer.
//
throw new PackProtocolException(uri, MessageFormat.format(
JGitText.get().statelessRPCRequiresOptionToBeEnabled,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
index adc1c9849d..9a96fdcf37 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
@@ -122,7 +122,6 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
useBitmaps = transport.isPushUseBitmaps();
}
- /** {@inheritDoc} */
@Override
public void push(final ProgressMonitor monitor,
final Map<String, RemoteRefUpdate> refUpdates)
@@ -130,7 +129,6 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
push(monitor, refUpdates, null);
}
- /** {@inheritDoc} */
@Override
public void push(final ProgressMonitor monitor,
final Map<String, RemoteRefUpdate> refUpdates, OutputStream outputStream)
@@ -139,7 +137,6 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
doPush(monitor, refUpdates, outputStream);
}
- /** {@inheritDoc} */
@Override
protected TransportException noRepository(Throwable cause) {
// Sadly we cannot tell the "invalid URI" case from "push not allowed".
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
index f04e573feb..5299dfa981 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
@@ -147,13 +147,11 @@ class BundleFetchConnection extends BaseFetchConnection {
return line.toString();
}
- /** {@inheritDoc} */
@Override
public boolean didFetchTestConnectivity() {
return false;
}
- /** {@inheritDoc} */
@Override
protected void doFetch(final ProgressMonitor monitor,
final Collection<Ref> want, final Set<ObjectId> have)
@@ -174,13 +172,11 @@ class BundleFetchConnection extends BaseFetchConnection {
}
}
- /** {@inheritDoc} */
@Override
public void setPackLockMessage(String message) {
lockMessage = message;
}
- /** {@inheritDoc} */
@Override
public Collection<PackLock> getPackLocks() {
if (packLock != null)
@@ -258,7 +254,6 @@ class BundleFetchConnection extends BaseFetchConnection {
}
}
- /** {@inheritDoc} */
@Override
public void close() {
if (bin != null) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CapabilitiesV2Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CapabilitiesV2Request.java
index 9c5a54e9cb..27afdc29be 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CapabilitiesV2Request.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CapabilitiesV2Request.java
@@ -21,7 +21,11 @@ public final class CapabilitiesV2Request {
private CapabilitiesV2Request() {
}
- /** @return A builder of {@link CapabilitiesV2Request}. */
+ /**
+ * Create builder
+ *
+ * @return A builder of {@link CapabilitiesV2Request}.
+ */
public static Builder builder() {
return new Builder();
}
@@ -31,7 +35,11 @@ public final class CapabilitiesV2Request {
private Builder() {
}
- /** @return CapabilitiesV2Request */
+ /**
+ * Build the request
+ *
+ * @return CapabilitiesV2Request
+ */
public CapabilitiesV2Request build() {
return new CapabilitiesV2Request();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java
index 6a4cfca865..3bae0eae5a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java
@@ -39,7 +39,6 @@ public class ChainingCredentialsProvider extends CredentialsProvider {
Arrays.asList(providers));
}
- /** {@inheritDoc} */
@Override
public boolean isInteractive() {
for (CredentialsProvider p : credentialProviders)
@@ -48,7 +47,6 @@ public class ChainingCredentialsProvider extends CredentialsProvider {
return false;
}
- /** {@inheritDoc} */
@Override
public boolean supports(CredentialItem... items) {
for (CredentialsProvider p : credentialProviders)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ConnectivityChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ConnectivityChecker.java
index dcffe48be8..cbb3420db2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ConnectivityChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ConnectivityChecker.java
@@ -60,6 +60,8 @@ public interface ConnectivityChecker {
private RevWalk walk;
/**
+ * Get database we write the stored objects into
+ *
* @return database we write the stored objects into.
*/
public Repository getRepository() {
@@ -67,6 +69,8 @@ public interface ConnectivityChecker {
}
/**
+ * Set database we write the stored objects into
+ *
* @param repository
* set database we write the stored objects into.
*/
@@ -75,6 +79,8 @@ public interface ConnectivityChecker {
}
/**
+ * Get the parser used to parse pack
+ *
* @return the parser used to parse pack.
*/
public PackParser getParser() {
@@ -82,6 +88,8 @@ public interface ConnectivityChecker {
}
/**
+ * Set the parser
+ *
* @param parser
* the parser to set
*/
@@ -90,6 +98,8 @@ public interface ConnectivityChecker {
}
/**
+ * Whether checker should check objects
+ *
* @return if checker should check objects.
*/
public boolean isCheckObjects() {
@@ -97,6 +107,8 @@ public interface ConnectivityChecker {
}
/**
+ * Set whether objects should be checked
+ *
* @param checkObjects
* set if checker should check referenced objects outside of
* the received pack are reachable.
@@ -106,21 +118,27 @@ public interface ConnectivityChecker {
}
/**
- * @return command received by the current request.
+ * Get commands received by the current request
+ *
+ * @return commands received by the current request.
*/
public List<ReceiveCommand> getCommands() {
return commands;
}
/**
+ * Set commands received by the current request
+ *
* @param commands
- * set command received by the current request.
+ * commands received by the current request.
*/
public void setCommands(List<ReceiveCommand> commands) {
this.commands = commands;
}
/**
+ * Set the walk to parse commits
+ *
* @param walk
* the walk to parse commits
*/
@@ -129,6 +147,8 @@ public interface ConnectivityChecker {
}
/**
+ * Get the walk to parse commits
+ *
* @return the walk to parse commits
*/
public RevWalk getWalk() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialItem.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialItem.java
index 2b09424526..32852890c0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialItem.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialItem.java
@@ -109,14 +109,20 @@ public abstract class CredentialItem {
value = null;
}
- /** @return the current value */
+ /**
+ * Get value
+ *
+ * @return the current value
+ */
public String getValue() {
return value;
}
/**
+ * Set value
*
* @param newValue
+ * the new value
*/
public void setValue(String newValue) {
value = newValue;
@@ -214,7 +220,11 @@ public abstract class CredentialItem {
value = false;
}
- /** @return the current value */
+ /**
+ * Get value
+ *
+ * @return the current value
+ */
public boolean getValue() {
return value;
}
@@ -223,6 +233,7 @@ public abstract class CredentialItem {
* Set the new value.
*
* @param newValue
+ * the new value
*/
public void setValue(boolean newValue) {
value = newValue;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
index f02160e457..c510194ee6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
@@ -111,7 +111,7 @@ class FetchProcess {
for (PackLock lock : packLocks) {
lock.unlock();
}
- } catch (IOException e) {
+ } catch (Throwable e) {
if (e1 != null) {
e.addSuppressed(e1);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java
index 009a70b7b3..91eed96d4e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java
@@ -85,6 +85,8 @@ abstract class FetchRequest {
}
/**
+ * Get object ids in the "want" lines
+ *
* @return object ids in the "want" (and "want-ref") lines of the request
*/
@NonNull
@@ -93,6 +95,8 @@ abstract class FetchRequest {
}
/**
+ * Get the depth set in a "deepen" line
+ *
* @return the depth set in a "deepen" line. 0 by default.
*/
int getDepth() {
@@ -112,6 +116,8 @@ abstract class FetchRequest {
}
/**
+ * Get the filter spec given in a "filter" line
+ *
* @return the filter spec given in a "filter" line
*/
@NonNull
@@ -152,6 +158,8 @@ abstract class FetchRequest {
}
/**
+ * Get refs received in "deepen-not" lines
+ *
* @return refs received in "deepen-not" lines.
*/
@NonNull
@@ -160,6 +168,8 @@ abstract class FetchRequest {
}
/**
+ * Get string identifying the agent
+ *
* @return string identifying the agent (as sent in the request body by the
* client)
*/
@@ -169,8 +179,10 @@ abstract class FetchRequest {
}
/**
- * @return string identifying the client session ID (as sent in the request body by the
- * client)
+ * Get string identifying the client session ID
+ *
+ * @return string identifying the client session ID (as sent in the request
+ * body by the client)
*/
@Nullable
String getClientSID() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java
index ca3639d03c..53b3e19263 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java
@@ -58,6 +58,8 @@ final class FetchV0Request extends FetchRequest {
String clientSID;
/**
+ * Add wantId
+ *
* @param objectId
* object id received in a "want" line
* @return this builder
@@ -68,6 +70,8 @@ final class FetchV0Request extends FetchRequest {
}
/**
+ * Set depth
+ *
* @param d
* depth set in a "deepen" line
* @return this builder
@@ -78,6 +82,8 @@ final class FetchV0Request extends FetchRequest {
}
/**
+ * Get depth
+ *
* @return depth set in the request (via a "deepen" line). Defaulting to
* 0 if not set.
*/
@@ -86,6 +92,8 @@ final class FetchV0Request extends FetchRequest {
}
/**
+ * Whether there's at least one "deepen not" line
+ *
* @return true if there has been at least one "deepen not" line in the
* request so far
*/
@@ -94,6 +102,8 @@ final class FetchV0Request extends FetchRequest {
}
/**
+ * Add "deepen not"
+ *
* @param deepenNot
* reference received in a "deepen not" line
* @return this builder
@@ -104,6 +114,8 @@ final class FetchV0Request extends FetchRequest {
}
/**
+ * Set "deepen since"
+ *
* @param value
* Unix timestamp received in a "deepen since" line
* @return this builder
@@ -114,6 +126,8 @@ final class FetchV0Request extends FetchRequest {
}
/**
+ * Get "deepen since
+ *
* @return shallow since value, sent before in a "deepen since" line. 0
* by default.
*/
@@ -122,6 +136,8 @@ final class FetchV0Request extends FetchRequest {
}
/**
+ * Add client shallow commit
+ *
* @param shallowOid
* object id received in a "shallow" line
* @return this builder
@@ -132,6 +148,8 @@ final class FetchV0Request extends FetchRequest {
}
/**
+ * Add client capabilities
+ *
* @param clientCapabilities
* client capabilities sent by the client in the first want
* line of the request
@@ -143,6 +161,8 @@ final class FetchV0Request extends FetchRequest {
}
/**
+ * Set agent
+ *
* @param clientAgent
* agent line sent by the client in the request body
* @return this builder
@@ -153,6 +173,8 @@ final class FetchV0Request extends FetchRequest {
}
/**
+ * Set client session id
+ *
* @param clientSID
* session-id line sent by the client in the request body
* @return this builder
@@ -163,6 +185,8 @@ final class FetchV0Request extends FetchRequest {
}
/**
+ * Set filter spec
+ *
* @param filter
* the filter set in a filter line
* @return this builder
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java
index 3d4f38131c..21284db2c1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java
@@ -70,6 +70,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Get object ids received in the "have" lines
+ *
* @return object ids received in the "have" lines
*/
@NonNull
@@ -78,6 +80,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Get list of references received in "want-ref" lines
+ *
* @return list of references received in "want-ref" lines
*
* @since 5.4
@@ -88,6 +92,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Whether the request had a "done" line
+ *
* @return true if the request had a "done" line
*/
boolean wasDoneReceived() {
@@ -95,6 +101,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Whether the request had a "wait-for-done" line
+ *
* @return true if the request had a "wait-for-done" line
*/
boolean wasWaitForDoneReceived() {
@@ -115,6 +123,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Whether "sideband-all" was received
+ *
* @return true if "sideband-all" was received
*/
boolean getSidebandAll() {
@@ -126,7 +136,11 @@ public final class FetchV2Request extends FetchRequest {
return packfileUriProtocols;
}
- /** @return A builder of {@link FetchV2Request}. */
+ /**
+ * Get builder
+ *
+ * @return A builder of {@link FetchV2Request}.
+ */
static Builder builder() {
return new Builder();
}
@@ -171,6 +185,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Add object the peer has
+ *
* @param objectId
* object id received in a "have" line
* @return this builder
@@ -181,7 +197,7 @@ public final class FetchV2Request extends FetchRequest {
}
/**
- * Ref received in "want-ref" line and the object-id it refers to
+ * Add Ref received in "want-ref" line and the object-id it refers to
*
* @param refName
* reference name
@@ -193,6 +209,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Add client capability
+ *
* @param clientCapability
* capability line sent by the client
* @return this builder
@@ -203,6 +221,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Add object received in "want" line
+ *
* @param wantId
* object id received in a "want" line
* @return this builder
@@ -213,6 +233,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Add Object received in a "shallow" line
+ *
* @param shallowOid
* object id received in a "shallow" line
* @return this builder
@@ -223,6 +245,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Set depth received in "deepen" line
+ *
* @param d
* Depth received in a "deepen" line
* @return this builder
@@ -233,6 +257,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Get depth set in request
+ *
* @return depth set in the request (via a "deepen" line). Defaulting to
* 0 if not set.
*/
@@ -241,6 +267,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Whether there has been at least one ""deepen not" line
+ *
* @return true if there has been at least one "deepen not" line in the
* request so far
*/
@@ -249,6 +277,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Add reference received in a "deepen not" line
+ *
* @param deepenNot
* reference received in a "deepen not" line
* @return this builder
@@ -259,6 +289,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Set Unix timestamp received in a "deepen since" line
+ *
* @param value
* Unix timestamp received in a "deepen since" line
* @return this builder
@@ -269,6 +301,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Get shallow since value
+ *
* @return shallow since value, sent before in a "deepen since" line. 0
* by default.
*/
@@ -277,6 +311,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Set filter spec
+ *
* @param filter
* spec set in a "filter" line
* @return this builder
@@ -321,6 +357,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Set value of client-supplied session capability
+ *
* @param clientSIDValue
* the client-supplied session capability, without the
* leading "session-id="
@@ -347,7 +385,10 @@ public final class FetchV2Request extends FetchRequest {
}
/**
- * @param value true if client sent "sideband-all"
+ * Set whether client sent "sideband-all
+ *
+ * @param value
+ * true if client sent "sideband-all"
* @return this builder
*/
Builder setSidebandAll(boolean value) {
@@ -361,6 +402,8 @@ public final class FetchV2Request extends FetchRequest {
}
/**
+ * Build initialized fetch request
+ *
* @return Initialized fetch request
*/
FetchV2Request build() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java
index a8cf849fed..442b963440 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FilterSpec.java
@@ -55,7 +55,6 @@ public final class FilterSpec {
return val.testBit(type);
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ObjectTypes)) {
@@ -66,7 +65,6 @@ public final class FilterSpec {
return other.val.equals(val);
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
return val.hashCode();
@@ -139,6 +137,8 @@ public final class FilterSpec {
}
/**
+ * Specify permitted object types
+ *
* @param types
* set of permitted object types, for use in "blob:none" and
* "object:none" filters
@@ -149,6 +149,8 @@ public final class FilterSpec {
}
/**
+ * Specify blob limit
+ *
* @param blobLimit
* the blob limit in a "blob:[limit]" filter line
* @return a filter spec which filters blobs above a certain size
@@ -162,6 +164,8 @@ public final class FilterSpec {
}
/**
+ * Specify tree depth limit
+ *
* @param treeDepthLimit
* the tree depth limit in a "tree:[depth]" filter line
* @return a filter spec which filters blobs and trees beyond a certain tree
@@ -181,6 +185,8 @@ public final class FilterSpec {
public static final FilterSpec NO_FILTER = new FilterSpec(ObjectTypes.ALL, -1, -1);
/**
+ * Whether object type is allowed
+ *
* @param type
* a Git object type, such as
* {@link org.eclipse.jgit.lib.Constants#OBJ_BLOB}
@@ -193,6 +199,8 @@ public final class FilterSpec {
}
/**
+ * Get blob size limit
+ *
* @return -1 if this filter does not filter blobs based on size, or a
* non-negative integer representing the max size of blobs to allow
*/
@@ -201,6 +209,8 @@ public final class FilterSpec {
}
/**
+ * Get tree depth limit
+ *
* @return -1 if this filter does not filter blobs and trees based on depth,
* or a non-negative integer representing the max tree depth of
* blobs and trees to fetch
@@ -210,6 +220,8 @@ public final class FilterSpec {
}
/**
+ * Whether this filter is a no-op
+ *
* @return true if this filter doesn't filter out anything
*/
public boolean isNoOp() {
@@ -217,7 +229,10 @@ public final class FilterSpec {
}
/**
- * @return the filter line which describes this spec, e.g. "filter blob:limit=42"
+ * Get filter line describing this spec
+ *
+ * @return the filter line which describes this spec, e.g. "filter
+ * blob:limit=42"
*/
@Nullable
public String filterLine() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FtpChannel.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FtpChannel.java
index 44656c1397..5d334e67f7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FtpChannel.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FtpChannel.java
@@ -71,6 +71,7 @@ public interface FtpChannel {
* @param unit
* of the {@code timeout}
* @throws IOException
+ * if an IO error occurred
*/
void connect(int timeout, TimeUnit unit) throws IOException;
@@ -80,6 +81,8 @@ public interface FtpChannel {
void disconnect();
/**
+ * Whether the FtpChannel is connected
+ *
* @return whether the {@link FtpChannel} is connected
*/
boolean isConnected();
@@ -95,8 +98,11 @@ public interface FtpChannel {
void cd(String path) throws IOException;
/**
+ * Get current remote directory path
+ *
* @return the current remote directory path
* @throws IOException
+ * if an IO error occurred
*/
String pwd() throws IOException;
@@ -118,6 +124,7 @@ public interface FtpChannel {
* of the directory to list
* @return the directory entries
* @throws IOException
+ * if an IO error occurred
*/
Collection<DirEntry> ls(String path) throws IOException;
@@ -128,6 +135,7 @@ public interface FtpChannel {
* @param path
* to delete
* @throws IOException
+ * if an IO error occurred
*/
void rmdir(String path) throws IOException;
@@ -137,6 +145,7 @@ public interface FtpChannel {
* @param path
* to create
* @throws IOException
+ * if an IO error occurred
*/
void mkdir(String path) throws IOException;
@@ -148,6 +157,7 @@ public interface FtpChannel {
*
* @return the stream to read from
* @throws IOException
+ * if an IO error occurred
*/
InputStream get(String path) throws IOException;
@@ -160,6 +170,7 @@ public interface FtpChannel {
*
* @return the stream to read from
* @throws IOException
+ * if an IO error occurred
*/
OutputStream put(String path) throws IOException;
@@ -204,6 +215,7 @@ public interface FtpChannel {
* @param to
* new name of the file
* @throws IOException
+ * if an IO error occurred
* @see <a href=
* "http://pubs.opengroup.org/onlinepubs/9699919799/functions/rename.html">stdio.h:
* rename()</a>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
index 14d6c1ec14..655b4605e4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
@@ -35,9 +35,8 @@ public class HMACSHA1NonceGenerator implements NonceGenerator {
*
* @param seed
* seed the generator
- * @throws java.lang.IllegalStateException
*/
- public HMACSHA1NonceGenerator(String seed) throws IllegalStateException {
+ public HMACSHA1NonceGenerator(String seed) {
try {
byte[] keyBytes = seed.getBytes(ISO_8859_1);
SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1"); //$NON-NLS-1$
@@ -48,16 +47,13 @@ public class HMACSHA1NonceGenerator implements NonceGenerator {
}
}
- /** {@inheritDoc} */
@Override
- public synchronized String createNonce(Repository repo, long timestamp)
- throws IllegalStateException {
+ public synchronized String createNonce(Repository repo, long timestamp) {
String input = repo.getIdentifier() + ":" + String.valueOf(timestamp); //$NON-NLS-1$
byte[] rawHmac = mac.doFinal(input.getBytes(UTF_8));
return Long.toString(timestamp) + "-" + toHex(rawHmac); //$NON-NLS-1$
}
- /** {@inheritDoc} */
@Override
public NonceStatus verify(String received, String sent,
Repository db, boolean allowSlop, int slop) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
index aec5b89c7e..c2d8c1b73f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
@@ -140,7 +140,7 @@ abstract class HttpAuthMethod {
valuePart[0].toUpperCase(Locale.ROOT));
if ((ignoreTypes != null)
- && (ignoreTypes.contains(methodType))) {
+ && ignoreTypes.contains(methodType)) {
continue;
}
@@ -224,7 +224,9 @@ abstract class HttpAuthMethod {
* Update this method with the given username and password pair.
*
* @param user
+ * username
* @param pass
+ * password
*/
abstract void authorize(String user, String pass);
@@ -232,7 +234,9 @@ abstract class HttpAuthMethod {
* Update connection properties based on this authentication method.
*
* @param conn
+ * the connection to configure
* @throws IOException
+ * if an IO error occurred
*/
abstract void configureRequest(HttpConnection conn) throws IOException;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
index dc82f46197..f10b7bf452 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpConfig.java
@@ -101,7 +101,7 @@ public class HttpConfig {
private static final int DEFAULT_MAX_REDIRECTS = 5;
- private static final int MAX_REDIRECTS = (new Supplier<Integer>() {
+ private static final int MAX_REDIRECTS = new Supplier<Integer>() {
@Override
public Integer get() {
@@ -119,7 +119,7 @@ public class HttpConfig {
}
return value;
}
- }).get().intValue();
+ }.get().intValue();
private static final String ENV_HTTP_USER_AGENT = "GIT_HTTP_USER_AGENT"; //$NON-NLS-1$
@@ -302,8 +302,7 @@ public class HttpConfig {
int postBufferSize = config.getInt(HTTP, POST_BUFFER_KEY,
1 * 1024 * 1024);
boolean sslVerifyFlag = config.getBoolean(HTTP, SSL_VERIFY_KEY, true);
- HttpRedirectMode followRedirectsMode = config.getEnum(
- HttpRedirectMode.values(), HTTP, null,
+ HttpRedirectMode followRedirectsMode = config.getEnum(HTTP, null,
FOLLOW_REDIRECTS_KEY, HttpRedirectMode.INITIAL);
int redirectLimit = config.getInt(HTTP, MAX_REDIRECTS_KEY,
MAX_REDIRECTS);
@@ -335,8 +334,8 @@ public class HttpConfig {
postBufferSize);
sslVerifyFlag = config.getBoolean(HTTP, match, SSL_VERIFY_KEY,
sslVerifyFlag);
- followRedirectsMode = config.getEnum(HttpRedirectMode.values(),
- HTTP, match, FOLLOW_REDIRECTS_KEY, followRedirectsMode);
+ followRedirectsMode = config.getEnum(HTTP, match,
+ FOLLOW_REDIRECTS_KEY, followRedirectsMode);
int newMaxRedirects = config.getInt(HTTP, match, MAX_REDIRECTS_KEY,
redirectLimit);
if (newMaxRedirects >= 0) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java
index 18dc792eb1..05736e1107 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalFetchConnection.java
@@ -70,6 +70,7 @@ class InternalFetchConnection<C> extends BasePackFetchConnection {
worker = new Thread("JGit-Upload-Pack") { //$NON-NLS-1$
@Override
+ @SuppressWarnings("CatchAndPrintStackTrace")
public void run() {
try {
final UploadPack rp = uploadPackFactory.create(req, remote);
@@ -103,7 +104,6 @@ class InternalFetchConnection<C> extends BasePackFetchConnection {
readAdvertisedRefs();
}
- /** {@inheritDoc} */
@Override
public void close() {
super.close();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java
index d2b0ef4a29..a23fdc94ad 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalPushConnection.java
@@ -99,7 +99,6 @@ class InternalPushConnection<C> extends BasePackPushConnection {
readAdvertisedRefs();
}
- /** {@inheritDoc} */
@Override
public void close() {
super.close();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java
index 856047ee19..008f1d9b20 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/LsRefsV2Request.java
@@ -53,22 +53,36 @@ public final class LsRefsV2Request {
this.clientSID = clientSID;
}
- /** @return ref prefixes that the client requested. */
+ /**
+ * Get ref prefixes
+ *
+ * @return ref prefixes that the client requested.
+ */
public List<String> getRefPrefixes() {
return refPrefixes;
}
- /** @return true if the client requests symbolic references. */
+ /**
+ * Whether the client requests symbolic references
+ *
+ * @return true if the client requests symbolic references.
+ */
public boolean getSymrefs() {
return symrefs;
}
- /** @return true if the client requests tags to be peeled. */
+ /**
+ * Whether the client requests tags to be peeled
+ *
+ * @return true if the client requests tags to be peeled.
+ */
public boolean getPeel() {
return peel;
}
/**
+ * Get agent reported by the client
+ *
* @return agent as reported by the client
*
* @since 5.2
@@ -79,6 +93,8 @@ public final class LsRefsV2Request {
}
/**
+ * Get session-id reported by the client
+ *
* @return session-id as reported by the client
*
* @since 6.4
@@ -106,7 +122,11 @@ public final class LsRefsV2Request {
return serverOptions;
}
- /** @return A builder of {@link LsRefsV2Request}. */
+ /**
+ * Create builder
+ *
+ * @return A builder of {@link LsRefsV2Request}.
+ */
public static Builder builder() {
return new Builder();
}
@@ -129,7 +149,10 @@ public final class LsRefsV2Request {
}
/**
+ * Set ref prefixes
+ *
* @param value
+ * ref prefix values
* @return the Builder
*/
public Builder setRefPrefixes(List<String> value) {
@@ -138,7 +161,10 @@ public final class LsRefsV2Request {
}
/**
+ * Set symrefs
+ *
* @param value
+ * of symrefs
* @return the Builder
*/
public Builder setSymrefs(boolean value) {
@@ -147,7 +173,10 @@ public final class LsRefsV2Request {
}
/**
+ * Set whether to peel tags
+ *
* @param value
+ * of peel
* @return the Builder
*/
public Builder setPeel(boolean value) {
@@ -203,7 +232,11 @@ public final class LsRefsV2Request {
return this;
}
- /** @return LsRefsV2Request */
+ /**
+ * Builds the request
+ *
+ * @return LsRefsV2Request the request
+ */
public LsRefsV2Request build() {
return new LsRefsV2Request(
Collections.unmodifiableList(refPrefixes), symrefs, peel,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java
index 6531b17e23..e32530cc9b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java
@@ -67,7 +67,6 @@ public class NetRCCredentialsProvider extends CredentialsProvider {
CredentialsProvider.setDefault(new NetRCCredentialsProvider());
}
- /** {@inheritDoc} */
@Override
public boolean supports(CredentialItem... items) {
for (CredentialItem i : items) {
@@ -81,7 +80,6 @@ public class NetRCCredentialsProvider extends CredentialsProvider {
return true;
}
- /** {@inheritDoc} */
@Override
public boolean get(URIish uri, CredentialItem... items)
throws UnsupportedCredentialItem {
@@ -112,7 +110,6 @@ public class NetRCCredentialsProvider extends CredentialsProvider {
return !isAnyNull(items);
}
- /** {@inheritDoc} */
@Override
public boolean isInteractive() {
return false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java
index 2541ff1393..30e80e0783 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NonceGenerator.java
@@ -31,10 +31,8 @@ public interface NonceGenerator {
* @param timestamp
* The current time in seconds.
* @return The nonce to be signed by the pusher
- * @throws java.lang.IllegalStateException
*/
- String createNonce(Repository db, long timestamp)
- throws IllegalStateException;
+ String createNonce(Repository db, long timestamp);
/**
* Verify trustworthiness of the received nonce.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectInfoRequest.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectInfoRequest.java
index 86a2716675..241f1e749f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectInfoRequest.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ObjectInfoRequest.java
@@ -34,12 +34,20 @@ public final class ObjectInfoRequest {
this.objectIDs = objectIDs;
}
- /** @return object IDs that the client requested. */
+ /**
+ * Get object ids requested by the client
+ *
+ * @return object IDs that the client requested.
+ */
public List<ObjectId> getObjectIDs() {
return this.objectIDs;
}
- /** @return A builder of {@link ObjectInfoRequest}. */
+ /**
+ * Create builder
+ *
+ * @return A builder of {@link ObjectInfoRequest}.
+ */
public static Builder builder() {
return new Builder();
}
@@ -52,7 +60,10 @@ public final class ObjectInfoRequest {
}
/**
+ * Set object ids
+ *
* @param value
+ * of objectIds
* @return the Builder
*/
public Builder setObjectIDs(List<ObjectId> value) {
@@ -60,7 +71,11 @@ public final class ObjectInfoRequest {
return this;
}
- /** @return ObjectInfoRequest */
+ /**
+ * Build the request
+ *
+ * @return ObjectInfoRequest the request
+ */
public ObjectInfoRequest build() {
return new ObjectInfoRequest(
Collections.unmodifiableList(objectIDs));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
index d9669044c7..e1f2b19ce5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
@@ -530,7 +530,7 @@ public abstract class PackParser {
receiving.beginTask(JGitText.get().receivingObjects,
(int) expectedObjectCount);
try {
- for (int done = 0; done < expectedObjectCount; done++) {
+ for (long done = 0; done < expectedObjectCount; done++) {
indexOneObject();
receiving.update(1);
if (receiving.isCancelled())
@@ -1102,6 +1102,7 @@ public abstract class PackParser {
* @param data
* raw content of the object.
* @throws org.eclipse.jgit.errors.CorruptObjectException
+ * if a corrupt object was found
* @since 4.9
*/
protected void verifySafeObject(final AnyObjectId id, final int type,
@@ -1565,7 +1566,7 @@ public abstract class PackParser {
* @param baseStreamPosition
* position of the base object in the incoming stream. The base
* must be before the delta, therefore {@code baseStreamPosition
- * &lt; deltaStreamPosition}. This is <b>not</b> the position
+ * < deltaStreamPosition}. This is <b>not</b> the position
* returned by a prior end object event.
* @param inflatedSize
* size of the delta when fully inflated. The size stored within
@@ -1667,17 +1668,27 @@ public abstract class PackParser {
long sizeBeforeInflating;
- /** @return offset within the input stream. */
+ /**
+ * Get offset within the input stream
+ *
+ * @return offset within the input stream.
+ */
public long getOffset() {
return position;
}
- /** @return the CRC-32 checksum of the stored delta data. */
+ /**
+ * Get the CRC-32 checksum of the stored delta data
+ *
+ * @return the CRC-32 checksum of the stored delta data.
+ */
public int getCRC() {
return crc;
}
/**
+ * Set the CRC-32 checksum of the stored delta data
+ *
* @param crc32
* the CRC-32 checksum of the stored delta data.
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java
index bf7997ec62..fae26cda95 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackedObjectInfo.java
@@ -117,6 +117,7 @@ public class PackedObjectInfo extends ObjectIdOwnerMap.Entry {
* Size in storage
*
* @param sizeBeforeInflating
+ * size before inflating
*/
void setSize(long sizeBeforeInflating) {
this.sizeBeforeInflating = sizeBeforeInflating;
@@ -147,6 +148,8 @@ public class PackedObjectInfo extends ObjectIdOwnerMap.Entry {
}
/**
+ * Get full size (inflated, undeltified)
+ *
* @return size of the object (inflated, undeltified)
*
* @since 6.4
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
index ed33eaed07..614ad88246 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
@@ -43,24 +43,13 @@ public class PacketLineIn {
/**
* Magic return from {@link #readString()} when a flush packet is found.
- *
- * @deprecated Callers should use {@link #isEnd(String)} to check if a
- * string is the end marker, or
- * {@link PacketLineIn#readStrings()} to iterate over all
- * strings in the input stream until the marker is reached.
*/
- @Deprecated
- public static final String END = new String(); /* must not string pool */
+ private static final String END = new String(); /* must not string pool */
/**
* Magic return from {@link #readString()} when a delim packet is found.
- *
- * @since 5.0
- * @deprecated Callers should use {@link #isDelimiter(String)} to check if a
- * string is the delimiter.
*/
- @Deprecated
- public static final String DELIM = new String(); /* must not string pool */
+ private static final String DELIM = new String(); /* must not string pool */
enum AckNackResult {
/** NAK */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java
index 77f0a7a516..43b742b02d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineOut.java
@@ -85,8 +85,10 @@ public class PacketLineOut {
}
/**
+ * Whether is using sideband
+ *
* @return whether to add a sideband designator to each non-flush and
- * non-delim packet
+ * non-delim packet
* @see #setUsingSideband
* @since 5.5
*/
@@ -95,11 +97,14 @@ public class PacketLineOut {
}
/**
- * @param value If true, when writing packet lines, add, as the first
- * byte, a sideband designator to each non-flush and non-delim
- * packet. See pack-protocol.txt and protocol-v2.txt from the Git
- * project for more information, specifically the "side-band" and
- * "sideband-all" sections.
+ * Set whether to use sideband
+ *
+ * @param value
+ * If true, when writing packet lines, add, as the first byte, a
+ * sideband designator to each non-flush and non-delim packet.
+ * See pack-protocol.txt and protocol-v2.txt from the Git project
+ * for more information, specifically the "side-band" and
+ * "sideband-all" sections.
* @since 5.5
*/
public void setUsingSideband(boolean value) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHookChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHookChain.java
index 4d53e1b261..f9d2c38ad3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHookChain.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostReceiveHookChain.java
@@ -47,7 +47,6 @@ public class PostReceiveHookChain implements PostReceiveHook {
}
}
- /** {@inheritDoc} */
@Override
public void onPostReceive(ReceivePack rp,
Collection<ReceiveCommand> commands) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHookChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHookChain.java
index 4334888a9a..072f43f41b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHookChain.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PostUploadHookChain.java
@@ -80,7 +80,6 @@ public class PostUploadHookChain implements PostUploadHook {
}
}
- /** {@inheritDoc} */
@Override
public void onPostUpload(PackStatistics stats) {
for (PostUploadHook hook : hooks) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHookChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHookChain.java
index dffa30da8e..2a5522bb4c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHookChain.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreReceiveHookChain.java
@@ -46,7 +46,6 @@ public class PreReceiveHookChain implements PreReceiveHook {
}
}
- /** {@inheritDoc} */
@Override
public void onPreReceive(ReceivePack rp,
Collection<ReceiveCommand> commands) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java
index 9c28abed95..dc703d0b11 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PreUploadHookChain.java
@@ -48,7 +48,6 @@ public class PreUploadHookChain implements PreUploadHook {
}
}
- /** {@inheritDoc} */
@Override
public void onBeginNegotiateRound(UploadPack up,
Collection<? extends ObjectId> wants, int cntOffered)
@@ -58,7 +57,6 @@ public class PreUploadHookChain implements PreUploadHook {
}
}
- /** {@inheritDoc} */
@Override
public void onEndNegotiateRound(UploadPack up,
Collection<? extends ObjectId> wants, int cntCommon,
@@ -69,7 +67,6 @@ public class PreUploadHookChain implements PreUploadHook {
}
}
- /** {@inheritDoc} */
@Override
public void onSendPack(UploadPack up,
Collection<? extends ObjectId> wants,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java
index 9d055519a5..f1afeab020 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java
@@ -51,7 +51,9 @@ final class ProtocolV0Parser {
* incoming lines. This method will read until an END line.
* @return a FetchV0Request with the data received in the wire.
* @throws PackProtocolException
+ * if a protocol occurred
* @throws IOException
+ * if an IO error occurred
*/
FetchV0Request recvWants(PacketLineIn pckIn)
throws PackProtocolException, IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java
index d7626df3fa..2caecbe843 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Hook.java
@@ -24,6 +24,8 @@ public interface ProtocolV2Hook {
};
/**
+ * Handle capabilities request
+ *
* @param req
* the capabilities request
* @throws ServiceMayNotContinueException
@@ -36,6 +38,8 @@ public interface ProtocolV2Hook {
}
/**
+ * Handle ls-refs request
+ *
* @param req
* the ls-refs request
* @throws ServiceMayNotContinueException
@@ -48,8 +52,12 @@ public interface ProtocolV2Hook {
}
/**
- * @param req the fetch request
- * @throws ServiceMayNotContinueException abort; the message will be sent to the user
+ * Handle fetch request
+ *
+ * @param req
+ * the fetch request
+ * @throws ServiceMayNotContinueException
+ * abort; the message will be sent to the user
*/
default void onFetch(FetchV2Request req)
throws ServiceMayNotContinueException {
@@ -57,6 +65,8 @@ public interface ProtocolV2Hook {
}
/**
+ * Handle object-info request
+ *
* @param req
* the object-info request
* @throws ServiceMayNotContinueException
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java
index d9831c7701..437abb0639 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificate.java
@@ -222,13 +222,11 @@ public class PushCertificate {
return sb;
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
return signature.hashCode();
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object o) {
if (!(o instanceof PushCertificate)) {
@@ -259,7 +257,6 @@ public class PushCertificate {
return true;
}
- /** {@inheritDoc} */
@Override
public String toString() {
return getClass().getSimpleName() + '['
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateIdent.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateIdent.java
index 94d1169abb..bd76b34aef 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateIdent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateIdent.java
@@ -228,20 +228,17 @@ public class PushCertificateIdent {
return tzOffset;
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object o) {
return (o instanceof PushCertificateIdent)
&& raw.equals(((PushCertificateIdent) o).raw);
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
return raw.hashCode();
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java
index 5009ecf8f3..463d05393f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java
@@ -10,8 +10,8 @@
package org.eclipse.jgit.transport;
-import static org.eclipse.jgit.transport.ReceivePack.parseCommand;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_CERT;
+import static org.eclipse.jgit.transport.ReceivePack.parseCommand;
import java.io.EOFException;
import java.io.IOException;
@@ -317,7 +317,7 @@ public class PushCertificateParser {
* certificate.
* <p>
* This method doesn't parse the first line {@code "push-cert \NUL
- * &lt;capabilities&gt;"}, but assumes the first line including the
+ * <capabilities>"}, but assumes the first line including the
* capabilities has already been handled by the caller.
*
* @param pckIn
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
index a9e93b6be6..6bdaf0e234 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
@@ -24,6 +24,7 @@ import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -329,7 +330,7 @@ public class PushCertificateStore implements AutoCloseable {
if (newId == null) {
return RefUpdate.Result.NO_CHANGE;
}
- try (ObjectInserter inserter = db.newObjectInserter()) {
+ try {
RefUpdate.Result result = updateRef(newId);
switch (result) {
case FAST_FORWARD:
@@ -404,8 +405,8 @@ public class PushCertificateStore implements AutoCloseable {
}
private static void sortPending(List<PendingCert> pending) {
- Collections.sort(pending, (PendingCert a, PendingCert b) -> Long.signum(
- a.ident.getWhen().getTime() - b.ident.getWhen().getTime()));
+ Collections.sort(pending,
+ Comparator.comparing((PendingCert a) -> a.ident.getWhenAsInstant()));
}
private DirCache newDirCache() throws IOException {
@@ -503,7 +504,7 @@ public class PushCertificateStore implements AutoCloseable {
} else {
sb.append(MessageFormat.format(
JGitText.get().storePushCertMultipleRefs,
- Integer.valueOf(cert.getCommands().size())));
+ cert.getCommands().size()));
}
return sb.append('\n').toString();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java
index 51c8558bfd..32c443f0f3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushConnection.java
@@ -43,17 +43,14 @@ public interface PushConnection extends Connection {
* and sending objects that remote repository need to have a consistent
* objects graph from new refs.
* <p>
- * <p>
* Only one call per connection is allowed. Subsequent calls will result in
* {@link org.eclipse.jgit.errors.TransportException}.
- * </p>
* <p>
* Implementation may use local repository to send a minimum set of objects
* needed by remote repository in efficient way.
* {@link org.eclipse.jgit.transport.Transport#isPushThin()} should be
* honored if applicable. refUpdates should be filled with information about
* status of each update.
- * </p>
*
* @param monitor
* progress monitor to update the end-user about the amount of
@@ -90,17 +87,14 @@ public interface PushConnection extends Connection {
* and sending objects that remote repository need to have a consistent
* objects graph from new refs.
* <p>
- * <p>
* Only one call per connection is allowed. Subsequent calls will result in
* {@link org.eclipse.jgit.errors.TransportException}.
- * </p>
* <p>
* Implementation may use local repository to send a minimum set of objects
* needed by remote repository in efficient way.
* {@link org.eclipse.jgit.transport.Transport#isPushThin()} should be
* honored if applicable. refUpdates should be filled with information about
* status of each update.
- * </p>
*
* @param monitor
* progress monitor to update the end-user about the amount of
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
index b59ae0c450..8f90f326d3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
@@ -76,6 +76,7 @@ class PushProcess {
* {@link PrePushHook} to run after the remote advertisement has
* been gotten
* @throws TransportException
+ * if a protocol error occurred during push/fetch
*/
PushProcess(Transport transport, Collection<RemoteRefUpdate> toPush,
PrePushHook prePush) throws TransportException {
@@ -96,6 +97,7 @@ class PushProcess {
* @param out
* OutputStream to write messages to
* @throws TransportException
+ * if a protocol error occurred during push/fetch
*/
PushProcess(Transport transport, Collection<RemoteRefUpdate> toPush,
PrePushHook prePush, OutputStream out) throws TransportException {
@@ -272,6 +274,7 @@ class PushProcess {
* {@link ObjectId} of the new commit
* @return {@code true} if the update fast-forwards, {@code false} otherwise
* @throws TransportException
+ * if a protocol error occurred during push/fetch
*/
private boolean isFastForward(ObjectId oldOid, ObjectId newOid)
throws TransportException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
index ab411600f1..bfc75f036c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
@@ -849,7 +849,6 @@ public class ReceiveCommand {
JGitText.get().lockError, err.getMessage()));
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
index 816cec89af..6f211e0794 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
@@ -88,44 +88,6 @@ import org.eclipse.jgit.util.io.TimeoutOutputStream;
* Implements the server side of a push connection, receiving objects.
*/
public class ReceivePack {
- /**
- * Data in the first line of a request, the line itself plus capabilities.
- *
- * @deprecated Use {@link FirstCommand} instead.
- * @since 5.6
- */
- @Deprecated
- public static class FirstLine {
- private final FirstCommand command;
-
- /**
- * Parse the first line of a receive-pack request.
- *
- * @param line
- * line from the client.
- */
- public FirstLine(String line) {
- command = FirstCommand.fromLine(line);
- }
-
- /** @return non-capabilities part of the line. */
- public String getLine() {
- return command.getLine();
- }
-
- /** @return capabilities parsed from the line. */
- public Set<String> getCapabilities() {
- Set<String> reconstructedCapabilites = new HashSet<>();
- for (Map.Entry<String, String> e : command.getCapabilities()
- .entrySet()) {
- String cap = e.getValue() == null ? e.getKey()
- : e.getKey() + "=" + e.getValue(); //$NON-NLS-1$
- reconstructedCapabilites.add(cap);
- }
-
- return reconstructedCapabilites;
- }
- }
/** Database we write the stored objects into. */
private final Repository db;
@@ -460,6 +422,7 @@ public class ReceivePack {
* null, assumes the default set of additional haves from the
* repository.
* @throws IOException
+ * if an IO error occurred
*/
public void setAdvertisedRefs(Map<String, Ref> allRefs,
Set<ObjectId> additionalHaves) throws IOException {
@@ -1024,6 +987,7 @@ public class ReceivePack {
* Set an error handler for {@link ReceiveCommand}.
*
* @param receiveCommandErrorHandler
+ * the error handler
* @since 5.7
*/
public void setReceiveCommandErrorHandler(
@@ -1212,6 +1176,7 @@ public class ReceivePack {
*
* @return advertised refs, or the default if not explicitly advertised.
* @throws IOException
+ * if an IO error occurred
*/
private Map<String, Ref> getAdvertisedOrDefaultRefs() throws IOException {
if (refs == null)
@@ -1348,7 +1313,8 @@ public class ReceivePack {
/**
* Receive a list of commands from the input.
*
- * @throws java.io.IOException
+ * @throws IOException
+ * if an IO error occurred
*/
private void recvCommands() throws IOException {
PacketLineIn pck = maxCommandBytes > 0
@@ -1936,7 +1902,8 @@ public class ReceivePack {
/**
* Close and flush (if necessary) the underlying streams.
*
- * @throws java.io.IOException
+ * @throws IOException
+ * if an IO error occurred
*/
private void close() throws IOException {
if (sideBand) {
@@ -2125,8 +2092,10 @@ public class ReceivePack {
}
/**
+ * Set the unpackErrorHandler
+ *
* @param unpackErrorHandler
- * the unpackErrorHandler to set
+ * the unpackErrorHandler
* @since 5.7
*/
public void setUnpackErrorHandler(UnpackErrorHandler unpackErrorHandler) {
@@ -2134,22 +2103,8 @@ public class ReceivePack {
}
/**
- * Set whether this class will report command failures as warning messages
- * before sending the command results.
+ * Get the client session-id
*
- * @param echo
- * if true this class will report command failures as warning
- * messages before sending the command results. This is usually
- * not necessary, but may help buggy Git clients that discard the
- * errors when all branches fail.
- * @deprecated no widely used Git versions need this any more
- */
- @Deprecated
- public void setEchoCommandFailures(boolean echo) {
- // No-op.
- }
-
- /**
* @return The client session-id.
* @since 6.4
*/
@@ -2174,6 +2129,7 @@ public class ReceivePack {
* standard error channel of the command execution. For most
* other network connections this should be null.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void receive(final InputStream input, final OutputStream output,
final OutputStream messages) throws IOException {
@@ -2217,6 +2173,7 @@ public class ReceivePack {
* standard error channel of the command execution. For most
* other network connections this should be null.
* @throws java.io.IOException
+ * if an IO error occurred
* @since 5.7
*/
public void receiveWithExceptionPropagation(InputStream input,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivedPackStatistics.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivedPackStatistics.java
index d7bc40006b..8887e263ea 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivedPackStatistics.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivedPackStatistics.java
@@ -174,7 +174,10 @@ public class ReceivedPackStatistics {
private long numDeltaTag;
/**
- * @param numBytesRead number of bytes read from the input stream
+ * Set number of bytes read from the input stream
+ *
+ * @param numBytesRead
+ * number of bytes read from the input stream
* @return this
*/
public Builder setNumBytesRead(long numBytesRead) {
@@ -183,6 +186,8 @@ public class ReceivedPackStatistics {
}
/**
+ * Increment additional bytes already in the local database
+ *
* @param size
* additional bytes already in the local database
* @return this
@@ -220,13 +225,21 @@ public class ReceivedPackStatistics {
return this;
}
- /** @return this */
+ /**
+ * Increment offset delta
+ *
+ * @return this
+ */
public Builder addOffsetDelta() {
numOfsDelta++;
return this;
}
- /** @return this */
+ /**
+ * Increment ref delta
+ *
+ * @return this
+ */
public Builder addRefDelta() {
numRefDelta++;
return this;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
index c525e66848..3d4bea2e48 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
@@ -161,9 +161,11 @@ public abstract class RefAdvertiser {
}
/**
+ * Set whether this advertiser should use protocol v2
+ *
* @param b
- * true if this advertiser should advertise using the protocol
- * v2 format, false otherwise
+ * true if this advertiser should advertise using the protocol v2
+ * format, false otherwise
* @since 5.0
*/
public void setUseProtocolV2(boolean b) {
@@ -173,10 +175,9 @@ public abstract class RefAdvertiser {
/**
* Toggle tag peeling.
* <p>
- * <p>
+ *
* This method must be invoked prior to any of the following:
* <ul>
- * <li>{@link #send(Map)}</li>
* <li>{@link #send(Collection)}</li>
* </ul>
*
@@ -193,7 +194,6 @@ public abstract class RefAdvertiser {
* <p>
* This method must be invoked prior to any of the following:
* <ul>
- * <li>{@link #send(Map)}</li>
* <li>{@link #send(Collection)}</li>
* <li>{@link #advertiseHave(AnyObjectId)}</li>
* </ul>
@@ -228,7 +228,6 @@ public abstract class RefAdvertiser {
* <p>
* This method must be invoked prior to any of the following:
* <ul>
- * <li>{@link #send(Map)}</li>
* <li>{@link #send(Collection)}</li>
* <li>{@link #advertiseHave(AnyObjectId)}</li>
* </ul>
@@ -258,24 +257,6 @@ public abstract class RefAdvertiser {
* @throws java.io.IOException
* the underlying output stream failed to write out an
* advertisement record.
- * @deprecated use {@link #send(Collection)} instead.
- */
- @Deprecated
- public Set<ObjectId> send(Map<String, Ref> refs) throws IOException {
- return send(refs.values());
- }
-
- /**
- * Format an advertisement for the supplied refs.
- *
- * @param refs
- * zero or more refs to format for the client. The collection is
- * sorted before display if necessary, and therefore may appear
- * in any order.
- * @return set of ObjectIds that were advertised to the client.
- * @throws java.io.IOException
- * the underlying output stream failed to write out an
- * advertisement record.
* @since 5.0
*/
public Set<ObjectId> send(Collection<Ref> refs) throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefLeaseSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefLeaseSpec.java
index 1af3fadadb..8a131c8971 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefLeaseSpec.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefLeaseSpec.java
@@ -58,7 +58,6 @@ public class RefLeaseSpec implements Serializable {
return expected;
}
- /** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder r = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
index 61d193593a..0466085b32 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
@@ -616,7 +616,6 @@ public class RefSpec implements Serializable {
return true;
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
int hc = 0;
@@ -627,7 +626,6 @@ public class RefSpec implements Serializable {
return hc;
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object obj) {
if (!(obj instanceof RefSpec))
@@ -649,7 +647,6 @@ public class RefSpec implements Serializable {
&& Objects.equals(getDestination(), b.getDestination());
}
- /** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder r = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
index 218e62c10a..fb3cd21d1a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
@@ -541,7 +541,6 @@ public class RemoteRefUpdate {
trackingRefUpdate.setResult(localUpdate.update(walk));
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
index 96c7be5b97..1f96be8e47 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
@@ -94,7 +94,6 @@ public class SideBandInputStream extends InputStream {
out = outputStream;
}
- /** {@inheritDoc} */
@Override
public int read() throws IOException {
needDataPacket();
@@ -104,7 +103,6 @@ public class SideBandInputStream extends InputStream {
return rawIn.read();
}
- /** {@inheritDoc} */
@Override
public int read(byte[] b, int off, int len) throws IOException {
int r = 0;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java
index bb80299fad..b6d840dd81 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java
@@ -94,14 +94,12 @@ public class SideBandOutputStream extends OutputStream {
writeBuffer();
}
- /** {@inheritDoc} */
@Override
public void flush() throws IOException {
flushBuffer();
out.flush();
}
- /** {@inheritDoc} */
@Override
public void write(byte[] b, int off, int len) throws IOException {
while (0 < len) {
@@ -128,7 +126,6 @@ public class SideBandOutputStream extends OutputStream {
}
}
- /** {@inheritDoc} */
@Override
public void write(int b) throws IOException {
if (cnt == buffer.length)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java
index 33308600d9..1e85d81084 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandProgressMonitor.java
@@ -28,7 +28,6 @@ class SideBandProgressMonitor extends BatchingProgressMonitor {
write = true;
}
- /** {@inheritDoc} */
@Override
protected void onUpdate(String taskName, int workCurr, Duration duration) {
StringBuilder s = new StringBuilder();
@@ -37,7 +36,6 @@ class SideBandProgressMonitor extends BatchingProgressMonitor {
send(s);
}
- /** {@inheritDoc} */
@Override
protected void onEndTask(String taskName, int workCurr, Duration duration) {
StringBuilder s = new StringBuilder();
@@ -54,7 +52,6 @@ class SideBandProgressMonitor extends BatchingProgressMonitor {
appendDuration(s, duration);
}
- /** {@inheritDoc} */
@Override
protected void onUpdate(String taskName, int cmp, int totalWork, int pcnt,
Duration duration) {
@@ -64,7 +61,6 @@ class SideBandProgressMonitor extends BatchingProgressMonitor {
send(s);
}
- /** {@inheritDoc} */
@Override
protected void onEndTask(String taskName, int cmp, int totalWork, int pcnt,
Duration duration) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigStore.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigStore.java
index 1226a6b5ea..b2e2549f12 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigStore.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigStore.java
@@ -43,6 +43,7 @@ public interface SshConfigStore {
* {@link SshConstants#CONNECTION_ATTEMPTS}, fill those values with defaults
* from the arguments:
* <table>
+ * <caption>Description of arguments</caption>
* <tr>
* <th>ssh config key</th>
* <th>value from argument</th>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
index 48cacf0964..41dbdcab51 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConstants.java
@@ -125,6 +125,25 @@ public final class SshConstants {
/** Key in an ssh config file. */
public static final String NUMBER_OF_PASSWORD_PROMPTS = "NumberOfPasswordPrompts";
+ /**
+ * Path to a shared library of a PKCS11 key provider, or "none".
+ * <p>
+ * If set and not "none", the provider's keys should be used.
+ * </p>
+ *
+ * @since 6.7
+ */
+ public static final String PKCS11_PROVIDER = "PKCS11Provider";
+
+ /**
+ * Non-standard JGit addition: specify the PKCS#11 slot list index of the
+ * token to use. A positive number; defaults to zero; ignored if negative
+ * (in which case zero is used, too).
+ *
+ * @since 6.7
+ */
+ public static final String PKCS11_SLOT_LIST_INDEX = "PKCS11SlotListIndex";
+
/** Key in an ssh config file. */
public static final String PORT = "Port";
@@ -146,8 +165,8 @@ public final class SshConstants {
* Comma-separated list of jump hosts, defining a jump host chain <em>in
* reverse order</em>. Each jump host is a SSH URI or "[user@]host[:port]".
* <p>
- * Reverse order means: to connect A->B->target, one can do in
- * {@code ~/.ssh/config} either of:
+ * Reverse order means: to connect {@literal A -> B -> target}, one can do
+ * in {@code ~/.ssh/config} either of:
* </p>
*
* <pre>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
index a0194ea8b1..8120df0698 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java
@@ -11,8 +11,6 @@
package org.eclipse.jgit.transport;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
import java.util.Iterator;
import java.util.ServiceLoader;
@@ -99,9 +97,8 @@ public abstract class SshSessionFactory {
* @since 5.2
*/
public static String getLocalUserName() {
- return AccessController
- .doPrivileged((PrivilegedAction<String>) () -> SystemReader
- .getInstance().getProperty(Constants.OS_USER_NAME_KEY));
+ return SystemReader.getInstance()
+ .getProperty(Constants.OS_USER_NAME_KEY);
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java
index 28ec92c706..8d93977a1d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java
@@ -108,7 +108,6 @@ public abstract class SshTransport extends TcpTransport {
return sock;
}
- /** {@inheritDoc} */
@Override
public void close() {
if (sock != null) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java
index 1985b66d87..77ab0f676d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TestProtocol.java
@@ -81,19 +81,16 @@ public class TestProtocol<C> extends TransportProtocol {
this.handles = new HashMap<>();
}
- /** {@inheritDoc} */
@Override
public String getName() {
return JGitText.get().transportProtoTest;
}
- /** {@inheritDoc} */
@Override
public Set<String> getSchemes() {
return Collections.singleton(SCHEME);
}
- /** {@inheritDoc} */
@Override
public Transport open(URIish uri, Repository local, String remoteName)
throws NotSupportedException, TransportException {
@@ -105,13 +102,11 @@ public class TestProtocol<C> extends TransportProtocol {
return new TransportInternal(local, uri, h);
}
- /** {@inheritDoc} */
@Override
public Set<URIishField> getRequiredFields() {
return EnumSet.of(URIishField.HOST, URIishField.PATH);
}
- /** {@inheritDoc} */
@Override
public Set<URIishField> getOptionalFields() {
return Collections.emptySet();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
index 51bc07cb94..26045a2c7c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java
@@ -174,7 +174,6 @@ public class TrackingRefUpdate {
}
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
index 064201a629..5333beff4b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
@@ -297,6 +297,8 @@ public class TransferConfig {
}
/**
+ * Whether clients are allowed to specify "filter" line
+ *
* @return true if clients are allowed to specify a "filter" line
* @since 5.0
*/
@@ -305,6 +307,8 @@ public class TransferConfig {
}
/**
+ * Whether clients are allowed to specify "want-ref" line
+ *
* @return true if clients are allowed to specify a "want-ref" line
* @since 5.1
*/
@@ -313,6 +317,8 @@ public class TransferConfig {
}
/**
+ * Whether the server accepts sideband-all requests
+ *
* @return true if the server accepts sideband-all requests (see
* {{@link #isAdvertiseSidebandAll()} for the advertisement)
* @since 5.5
@@ -322,6 +328,8 @@ public class TransferConfig {
}
/**
+ * Whether to advertise sideband all to the clients
+ *
* @return true to advertise sideband all to the clients
* @since 5.6
*/
@@ -330,6 +338,8 @@ public class TransferConfig {
}
/**
+ * Whether to advertise wait-for-done all to the clients
+ *
* @return true to advertise wait-for-done all to the clients
* @since 5.13
*/
@@ -338,6 +348,8 @@ public class TransferConfig {
}
/**
+ * Whether to advertise object-info to all clients
+ *
* @return true to advertise object-info to all clients
* @since 5.13
*/
@@ -346,6 +358,8 @@ public class TransferConfig {
}
/**
+ * Whether to advertise and receive session-id capability
+ *
* @return true to advertise and receive session-id capability
* @since 6.4
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
index ee35f4866e..ac76e83d34 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -33,10 +33,8 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jgit.annotations.NonNull;
@@ -76,6 +74,7 @@ public abstract class Transport implements AutoCloseable {
PUSH;
}
+ // Use weak references to enable unloading dynamically loaded protocols
private static final List<WeakReference<TransportProtocol>> protocols =
new CopyOnWriteArrayList<>();
@@ -108,7 +107,7 @@ public abstract class Transport implements AutoCloseable {
String name = prefix + Transport.class.getName();
return ldr.getResources(name);
} catch (IOException err) {
- return new Vector<URL>().elements();
+ return Collections.emptyEnumeration();
}
}
@@ -191,11 +190,13 @@ public abstract class Transport implements AutoCloseable {
* @param proto
* the exact object previously given to register.
*/
+ @SuppressWarnings("ModifyCollectionInEnhancedForLoop")
public static void unregister(TransportProtocol proto) {
for (WeakReference<TransportProtocol> ref : protocols) {
TransportProtocol refProto = ref.get();
- if (refProto == null || refProto == proto)
+ if (refProto == null || refProto == proto) {
protocols.remove(ref);
+ }
}
}
@@ -204,15 +205,17 @@ public abstract class Transport implements AutoCloseable {
*
* @return an immutable copy of the currently registered protocols.
*/
+ @SuppressWarnings("ModifyCollectionInEnhancedForLoop")
public static List<TransportProtocol> getTransportProtocols() {
int cnt = protocols.size();
List<TransportProtocol> res = new ArrayList<>(cnt);
for (WeakReference<TransportProtocol> ref : protocols) {
TransportProtocol proto = ref.get();
- if (proto != null)
+ if (proto != null) {
res.add(proto);
- else
+ } else {
protocols.remove(ref);
+ }
}
return Collections.unmodifiableList(res);
}
@@ -508,6 +511,7 @@ public abstract class Transport implements AutoCloseable {
* @throws org.eclipse.jgit.errors.TransportException
* the transport cannot open this URI.
*/
+ @SuppressWarnings("ModifyCollectionInEnhancedForLoop")
public static Transport open(Repository local, URIish uri, String remoteName)
throws NotSupportedException, TransportException {
for (WeakReference<TransportProtocol> ref : protocols) {
@@ -533,11 +537,15 @@ public abstract class Transport implements AutoCloseable {
* Note that the resulting transport instance can not be used for fetching
* or pushing, but only for reading remote refs.
*
- * @param uri a {@link org.eclipse.jgit.transport.URIish} object.
+ * @param uri
+ * a {@link org.eclipse.jgit.transport.URIish} object.
* @return new Transport instance
* @throws org.eclipse.jgit.errors.NotSupportedException
+ * case that is not supported by JGit
* @throws org.eclipse.jgit.errors.TransportException
+ * if transport failed
*/
+ @SuppressWarnings("ModifyCollectionInEnhancedForLoop")
public static Transport open(URIish uri) throws NotSupportedException, TransportException {
for (WeakReference<TransportProtocol> ref : protocols) {
TransportProtocol proto = ref.get();
@@ -546,8 +554,9 @@ public abstract class Transport implements AutoCloseable {
continue;
}
- if (proto.canHandle(uri, null, null))
+ if (proto.canHandle(uri, null, null)) {
return proto.open(uri);
+ }
}
throw new NotSupportedException(MessageFormat.format(JGitText.get().URINotSupported, uri));
@@ -584,7 +593,7 @@ public abstract class Transport implements AutoCloseable {
Collection<RefSpec> fetchSpecs) throws IOException {
if (fetchSpecs == null)
fetchSpecs = Collections.emptyList();
- final List<RemoteRefUpdate> result = new LinkedList<>();
+ final List<RemoteRefUpdate> result = new ArrayList<>();
final Collection<RefSpec> procRefs = expandPushWildcardsFor(db, specs);
for (RefSpec spec : procRefs) {
@@ -1112,28 +1121,8 @@ public abstract class Transport implements AutoCloseable {
}
/**
- * @return the blob limit value set with {@link #setFilterBlobLimit} or
- * {@link #setFilterSpec(FilterSpec)}, or -1 if no blob limit value
- * was set
- * @since 5.0
- * @deprecated Use {@link #getFilterSpec()} instead
- */
- @Deprecated
- public final long getFilterBlobLimit() {
- return filterSpec.getBlobLimit();
- }
-
- /**
- * @param bytes exclude blobs of size greater than this
- * @since 5.0
- * @deprecated Use {@link #setFilterSpec(FilterSpec)} instead
- */
- @Deprecated
- public final void setFilterBlobLimit(long bytes) {
- setFilterSpec(FilterSpec.withBlobLimit(bytes));
- }
-
- /**
+ * Get filter spec
+ *
* @return the last filter spec set with {@link #setFilterSpec(FilterSpec)},
* or {@link FilterSpec#NO_FILTER} if it was never invoked.
* @since 5.4
@@ -1143,7 +1132,10 @@ public abstract class Transport implements AutoCloseable {
}
/**
- * @param filter a new filter to use for this transport
+ * Set filter spec
+ *
+ * @param filter
+ * a new filter to use for this transport
* @since 5.4
*/
public final void setFilterSpec(@NonNull FilterSpec filter) {
@@ -1192,6 +1184,8 @@ public abstract class Transport implements AutoCloseable {
}
/**
+ * Get deepen-since
+ *
* @return the deepen-since for a shallow clone
* @since 6.3
*/
@@ -1210,7 +1204,9 @@ public abstract class Transport implements AutoCloseable {
}
/**
- * @return the deepen-not for a shallow clone
+ * Get list of deepen-not
+ *
+ * @return the list of deepen-not for a shallow clone
* @since 6.3
*/
public final List<String> getDeepenNots() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
index 784f566159..58232a7dde 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java
@@ -170,7 +170,6 @@ public class TransportAmazonS3 extends HttpTransport implements WalkTransport {
}
}
- /** {@inheritDoc} */
@Override
public FetchConnection openFetch() throws TransportException {
final DatabaseS3 c = new DatabaseS3(bucket, keyPrefix + "/objects"); //$NON-NLS-1$
@@ -179,7 +178,6 @@ public class TransportAmazonS3 extends HttpTransport implements WalkTransport {
return r;
}
- /** {@inheritDoc} */
@Override
public PushConnection openPush() throws TransportException {
final DatabaseS3 c = new DatabaseS3(bucket, keyPrefix + "/objects"); //$NON-NLS-1$
@@ -188,7 +186,6 @@ public class TransportAmazonS3 extends HttpTransport implements WalkTransport {
return r;
}
- /** {@inheritDoc} */
@Override
public void close() {
// No explicit connections are maintained.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java
index 04ebddb107..0f84f7e21e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleFile.java
@@ -105,7 +105,6 @@ class TransportBundleFile extends Transport implements TransportBundle {
bundle = bundlePath;
}
- /** {@inheritDoc} */
@Override
public FetchConnection openFetch() throws NotSupportedException,
TransportException {
@@ -121,14 +120,12 @@ class TransportBundleFile extends Transport implements TransportBundle {
return new BundleFetchConnection(this, src);
}
- /** {@inheritDoc} */
@Override
public PushConnection openPush() throws NotSupportedException {
throw new NotSupportedException(
JGitText.get().pushIsNotSupportedForBundleTransport);
}
- /** {@inheritDoc} */
@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
index eac9208a3a..0366bf3aa2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportBundleStream.java
@@ -57,7 +57,6 @@ public class TransportBundleStream extends Transport implements TransportBundle
src = in;
}
- /** {@inheritDoc} */
@Override
public FetchConnection openFetch() throws TransportException {
if (src == null)
@@ -69,14 +68,12 @@ public class TransportBundleStream extends Transport implements TransportBundle
}
}
- /** {@inheritDoc} */
@Override
public PushConnection openPush() throws NotSupportedException {
throw new NotSupportedException(
JGitText.get().pushIsNotSupportedForBundleTransport);
}
- /** {@inheritDoc} */
@Override
public void close() {
if (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
index a1914b6182..43b3f6cce0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitAnon.java
@@ -89,7 +89,6 @@ class TransportGitAnon extends TcpTransport implements PackTransport {
super(uri);
}
- /** {@inheritDoc} */
@Override
public FetchConnection openFetch() throws TransportException {
return new TcpFetchConnection();
@@ -102,13 +101,11 @@ class TransportGitAnon extends TcpTransport implements PackTransport {
return new TcpFetchConnection(refSpecs, additionalPatterns);
}
- /** {@inheritDoc} */
@Override
public PushConnection openPush() throws TransportException {
return new TcpPushConnection();
}
- /** {@inheritDoc} */
@Override
public void close() {
// Resources must be established per-connection.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
index 19ed4fbcc1..f77b04110d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java
@@ -140,7 +140,6 @@ public class TransportGitSsh extends SshTransport implements PackTransport {
}
}
- /** {@inheritDoc} */
@Override
public FetchConnection openFetch() throws TransportException {
return new SshFetchConnection();
@@ -153,7 +152,6 @@ public class TransportGitSsh extends SshTransport implements PackTransport {
return new SshFetchConnection(refSpecs, additionalPatterns);
}
- /** {@inheritDoc} */
@Override
public PushConnection openPush() throws TransportException {
return new SshPushConnection();
@@ -162,7 +160,7 @@ public class TransportGitSsh extends SshTransport implements PackTransport {
String commandFor(String exe) {
String path = uri.getPath();
if (uri.getScheme() != null && uri.getPath().startsWith("/~")) //$NON-NLS-1$
- path = (uri.getPath().substring(1));
+ path = uri.getPath().substring(1);
final StringBuilder cmd = new StringBuilder();
cmd.append(exe);
@@ -256,6 +254,12 @@ public class TransportGitSsh extends SshTransport implements PackTransport {
pb.environment().put(Constants.GIT_DIR_KEY,
directory.getPath());
}
+ File commonDirectory = local != null ? local.getCommonDirectory()
+ : null;
+ if (commonDirectory != null) {
+ pb.environment().put(Constants.GIT_COMMON_DIR_KEY,
+ commonDirectory.getPath());
+ }
return pb;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
index 405373a0f9..c9a48bf0e3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
@@ -66,7 +66,6 @@ import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -95,7 +94,6 @@ import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.lib.SymbolicRef;
-import org.eclipse.jgit.transport.HttpAuthMethod.Type;
import org.eclipse.jgit.transport.HttpConfig.HttpRedirectMode;
import org.eclipse.jgit.transport.http.HttpConnection;
import org.eclipse.jgit.transport.http.HttpConnectionFactory;
@@ -108,6 +106,7 @@ import org.eclipse.jgit.util.StringUtils;
import org.eclipse.jgit.util.SystemReader;
import org.eclipse.jgit.util.TemporaryBuffer;
import org.eclipse.jgit.util.io.DisabledOutputStream;
+import org.eclipse.jgit.util.io.SilentInputStream;
import org.eclipse.jgit.util.io.UnionInputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -311,6 +310,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
* @param uri
* a {@link org.eclipse.jgit.transport.URIish} object.
* @throws org.eclipse.jgit.errors.NotSupportedException
+ * if URI is not supported by JGit
* @since 4.9
*/
protected void setURI(URIish uri) throws NotSupportedException {
@@ -327,7 +327,9 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
* Create a minimal HTTP transport with default configuration values.
*
* @param uri
+ * URI to create a HTTP transport for
* @throws NotSupportedException
+ * if URI is not supported by JGit
*/
TransportHttp(URIish uri) throws NotSupportedException {
super(uri);
@@ -445,7 +447,6 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
}
}
- /** {@inheritDoc} */
@Override
public FetchConnection openFetch() throws TransportException,
NotSupportedException {
@@ -532,7 +533,6 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
return new BufferedReader(new InputStreamReader(in, UTF_8));
}
- /** {@inheritDoc} */
@Override
public PushConnection openPush() throws NotSupportedException,
TransportException {
@@ -567,7 +567,6 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
return p;
}
- /** {@inheritDoc} */
@Override
public void close() {
if (gitSession != null) {
@@ -588,6 +587,17 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
this.headers = headers;
}
+ /**
+ * Get additional headers on the HTTP connection
+ *
+ * @return unmodifiable map of additional name:values that are set as
+ * headers on the HTTP connection
+ * @since 6.6
+ */
+ public Map<String, String> getAdditionalHeaders() {
+ return Collections.unmodifiableMap(headers);
+ }
+
private NoRemoteRepositoryException createNotFoundException(URIish u,
URL url, String msg) {
String text;
@@ -636,7 +646,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
}
int authAttempts = 1;
int redirects = 0;
- Collection<Type> ignoreTypes = null;
+ Collection<HttpAuthMethod.Type> ignoreTypes = null;
for (;;) {
try {
final HttpConnection conn = httpOpen(METHOD_GET, u, AcceptEncoding.GZIP);
@@ -753,7 +763,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
void processResponseCookies(HttpConnection conn) {
if (cookieFile != null && http.getSaveCookies()) {
- List<HttpCookie> foundCookies = new LinkedList<>();
+ List<HttpCookie> foundCookies = new ArrayList<>();
List<String> cookieHeaderValues = conn
.getHeaderFields(HDR_SET_COOKIE);
@@ -785,7 +795,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
private List<HttpCookie> extractCookies(String headerKey,
List<String> headerValues) {
- List<HttpCookie> foundCookies = new LinkedList<>();
+ List<HttpCookie> foundCookies = new ArrayList<>();
for (String headerValue : headerValues) {
foundCookies
.addAll(HttpCookie.parse(headerKey + ':' + headerValue));
@@ -811,7 +821,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
/**
* Trust the server for all git operations from this repository; may be
* {@code null} if the transport was created via
- * {@link #TransportHttp(URIish)}.
+ * {@link TransportHttp#TransportHttp(URIish)}.
*/
CredentialItem.YesNoType forRepo;
@@ -1024,11 +1034,15 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
/**
* Open an HTTP connection.
*
- * @param method HTTP request method
- * @param u url of the HTTP connection
- * @param acceptEncoding accept-encoding header option
+ * @param method
+ * HTTP request method
+ * @param u
+ * url of the HTTP connection
+ * @param acceptEncoding
+ * accept-encoding header option
* @return the HTTP connection
* @throws java.io.IOException
+ * if an IO error occurred
* @since 4.6
*/
protected HttpConnection httpOpen(String method, URL u,
@@ -1545,7 +1559,8 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
throws TransportException {
svc = new MultiRequestService(SVC_UPLOAD_PACK,
getProtocolVersion());
- try (InputStream svcIn = svc.getInputStream();
+ try (InputStream svcIn = new SilentInputStream(
+ svc.getInputStream());
OutputStream svcOut = svc.getOutputStream()) {
init(svcIn, svcOut);
super.doFetch(monitor, want, have, outputStream);
@@ -1651,7 +1666,8 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
}
HttpAuthMethod authenticator = null;
- Collection<Type> ignoreTypes = EnumSet.noneOf(Type.class);
+ Collection<HttpAuthMethod.Type> ignoreTypes = EnumSet
+ .noneOf(HttpAuthMethod.Type.class);
// Counts number of repeated authentication attempts using the same
// authentication scheme
int authAttempts = 1;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
index 77d1419ea2..1b9431ce6e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportLocal.java
@@ -151,7 +151,6 @@ class TransportLocal extends Transport implements PackTransport {
}
}
- /** {@inheritDoc} */
@Override
public FetchConnection openFetch() throws TransportException {
return openFetch(Collections.emptyList());
@@ -170,7 +169,6 @@ class TransportLocal extends Transport implements PackTransport {
return new InternalFetchConnection<>(this, upf, null, openRepo());
}
- /** {@inheritDoc} */
@Override
public PushConnection openPush() throws TransportException {
final String rp = getOptionReceivePack();
@@ -183,7 +181,6 @@ class TransportLocal extends Transport implements PackTransport {
return new InternalPushConnection<>(this, rpf, null, openRepo());
}
- /** {@inheritDoc} */
@Override
public void close() {
// Resources must be established per-connection.
@@ -228,6 +225,7 @@ class TransportLocal extends Transport implements PackTransport {
env.remove("GIT_CONFIG"); //$NON-NLS-1$
env.remove("GIT_CONFIG_PARAMETERS"); //$NON-NLS-1$
env.remove("GIT_DIR"); //$NON-NLS-1$
+ env.remove("GIT_COMMON_DIR"); //$NON-NLS-1$
env.remove("GIT_WORK_TREE"); //$NON-NLS-1$
env.remove("GIT_GRAFT_FILE"); //$NON-NLS-1$
env.remove("GIT_INDEX_FILE"); //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportProtocol.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportProtocol.java
index 912a90a1bf..bfb840d75f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportProtocol.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportProtocol.java
@@ -251,7 +251,9 @@ public abstract class TransportProtocol {
* a {@link org.eclipse.jgit.transport.URIish} object.
* @return new Transport
* @throws org.eclipse.jgit.errors.NotSupportedException
+ * this protocol does not support the URI.
* @throws org.eclipse.jgit.errors.TransportException
+ * the transport cannot open this URI.
*/
public Transport open(URIish uri)
throws NotSupportedException, TransportException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
index b9feeb9c02..a59d352e0d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java
@@ -104,7 +104,6 @@ public class TransportSftp extends SshTransport implements WalkTransport {
super(local, uri);
}
- /** {@inheritDoc} */
@Override
public FetchConnection openFetch() throws TransportException {
final SftpObjectDB c = new SftpObjectDB(uri.getPath());
@@ -113,7 +112,6 @@ public class TransportSftp extends SshTransport implements WalkTransport {
return r;
}
- /** {@inheritDoc} */
@Override
public PushConnection openPush() throws TransportException {
final SftpObjectDB c = new SftpObjectDB(uri.getPath());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
index c9bb89a436..7b5842b712 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
@@ -82,7 +82,7 @@ public class URIish implements Serializable {
* Part of a pattern which matches a relative path. Relative paths don't
* start with slash or drive letters. Defines no capturing group.
*/
- private static final String RELATIVE_PATH_P = "(?:(?:[^\\\\/]+[\\\\/]+)*[^\\\\/]+[\\\\/]*)"; //$NON-NLS-1$
+ private static final String RELATIVE_PATH_P = "(?:(?:[^\\\\/]+[\\\\/]+)*+[^\\\\/]*)"; //$NON-NLS-1$
/**
* Part of a pattern which matches a relative or absolute path. Defines no
@@ -94,7 +94,7 @@ public class URIish implements Serializable {
private static final long serialVersionUID = 1L;
/**
- * A pattern matching standard URI: </br>
+ * A pattern matching standard URI: <br>
* <code>scheme "://" user_password? hostname? portnumber? path</code>
*/
private static final Pattern FULL_URI = Pattern.compile("^" // //$NON-NLS-1$
@@ -120,7 +120,7 @@ public class URIish implements Serializable {
* path (maybe even containing windows drive-letters) or a relative path.
*/
private static final Pattern LOCAL_FILE = Pattern.compile("^" // //$NON-NLS-1$
- + "([\\\\/]?" + PATH_P + ")" // //$NON-NLS-1$ //$NON-NLS-2$
+ + "([\\\\/]?+" + PATH_P + ")" // //$NON-NLS-1$ //$NON-NLS-2$
+ "$"); //$NON-NLS-1$
/**
@@ -175,6 +175,7 @@ public class URIish implements Serializable {
* @param s
* a {@link java.lang.String} object.
* @throws java.net.URISyntaxException
+ * if {@code s} was null or couldn't be parsed
*/
public URIish(String s) throws URISyntaxException {
if (StringUtils.isEmptyOrNull(s)) {
@@ -477,6 +478,7 @@ public class URIish implements Serializable {
* the new value for path.
* @return a new URI with the updated value.
* @throws java.net.URISyntaxException
+ * if URI couldn't be parsed from String
*/
public URIish setRawPath(String n) throws URISyntaxException {
final URIish r = new URIish(this);
@@ -551,7 +553,6 @@ public class URIish implements Serializable {
return r;
}
- /** {@inheritDoc} */
@Override
public int hashCode() {
int hc = 0;
@@ -570,7 +571,6 @@ public class URIish implements Serializable {
return hc;
}
- /** {@inheritDoc} */
@Override
public boolean equals(Object obj) {
if (!(obj instanceof URIish))
@@ -611,7 +611,6 @@ public class URIish implements Serializable {
return format(true, false);
}
- /** {@inheritDoc} */
@Override
public String toString() {
return format(false, false);
@@ -683,7 +682,8 @@ public class URIish implements Serializable {
/**
* Get the "humanish" part of the path. Some examples of a 'humanish' part
* for a full path:
- * <table summary="path vs humanish path" border="1">
+ * <table border="1">
+ * <caption>path vs. humanish path</caption>
* <tr>
* <th>Path</th>
* <th>Humanish part</th>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
index f245eae39f..41ab8acf05 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -30,11 +30,11 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_MULTI_ACK_D
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_DONE;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_PROGRESS;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_OFS_DELTA;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SESSION_ID;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SHALLOW;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDEBAND_ALL;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K;
-import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SESSION_ID;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WAIT_FOR_DONE;
import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ACK;
@@ -80,7 +80,6 @@ import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.pack.CachedPackUriProvider;
import org.eclipse.jgit.internal.storage.pack.PackWriter;
-import org.eclipse.jgit.internal.transport.parser.FirstWant;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
@@ -118,13 +117,13 @@ public class UploadPack implements Closeable {
/** Policy the server uses to validate client requests */
public enum RequestPolicy {
/** Client may only ask for objects the server advertised a reference for. */
- ADVERTISED,
+ ADVERTISED(0x08),
/**
* Client may ask for any commit reachable from a reference advertised by
* the server.
*/
- REACHABLE_COMMIT,
+ REACHABLE_COMMIT(0x02),
/**
* Client may ask for objects that are the tip of any reference, even if not
@@ -134,18 +133,36 @@ public class UploadPack implements Closeable {
*
* @since 3.1
*/
- TIP,
+ TIP(0x01),
/**
* Client may ask for any commit reachable from any reference, even if that
- * reference wasn't advertised.
+ * reference wasn't advertised, implies REACHABLE_COMMIT and TIP.
*
* @since 3.1
*/
- REACHABLE_COMMIT_TIP,
+ REACHABLE_COMMIT_TIP(0x03),
+
+ /** Client may ask for any SHA-1 in the repository, implies REACHABLE_COMMIT_TIP. */
+ ANY(0x07);
+
+ private final int bitmask;
- /** Client may ask for any SHA-1 in the repository. */
- ANY;
+ RequestPolicy(int bitmask) {
+ this.bitmask = bitmask;
+ }
+
+ /**
+ * Check if the current policy implies another, based on its bitmask.
+ *
+ * @param implied
+ * the implied policy based on its bitmask.
+ * @return true if the policy is implied.
+ * @since 6.10.1
+ */
+ public boolean implies(RequestPolicy implied) {
+ return (bitmask & implied.bitmask) != 0;
+ }
}
/**
@@ -172,44 +189,6 @@ public class UploadPack implements Closeable {
throws PackProtocolException, IOException;
}
- /**
- * Data in the first line of a want-list, the line itself plus options.
- *
- * @deprecated Use {@link FirstWant} instead
- */
- @Deprecated
- public static class FirstLine {
-
- private final FirstWant firstWant;
-
- /**
- * @param line
- * line from the client.
- */
- public FirstLine(String line) {
- try {
- firstWant = FirstWant.fromLine(line);
- } catch (PackProtocolException e) {
- throw new UncheckedIOException(e);
- }
- }
-
- /** @return non-capabilities part of the line. */
- public String getLine() {
- return firstWant.getLine();
- }
-
- /** @return capabilities parsed from the line. */
- public Set<String> getOptions() {
- if (firstWant.getAgent() != null) {
- Set<String> caps = new HashSet<>(firstWant.getCapabilities());
- caps.add(OPTION_AGENT + '=' + firstWant.getAgent());
- return caps;
- }
- return firstWant.getCapabilities();
- }
- }
-
/*
* {@link java.util.function.Consumer} doesn't allow throwing checked
* exceptions. Define our own to propagate IOExceptions.
@@ -732,8 +711,11 @@ public class UploadPack implements Closeable {
}
/**
- * @param p provider of URIs corresponding to cached packs (to support
- * the packfile URIs feature)
+ * Set provider of cached pack URIs
+ *
+ * @param p
+ * provider of URIs corresponding to cached packs (to support the
+ * packfile URIs feature)
* @since 5.5
*/
public void setCachedPackUriProvider(@Nullable CachedPackUriProvider p) {
@@ -771,9 +753,13 @@ public class UploadPack implements Closeable {
* its own error handling mechanism.
*
* @param input
+ * input stream
* @param output
+ * output stream
* @param messages
+ * stream for messages
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void upload(InputStream input, OutputStream output,
@Nullable OutputStream messages) throws IOException {
@@ -1175,6 +1161,11 @@ public class UploadPack implements Closeable {
}
private void fetchV2(PacketLineOut pckOut) throws IOException {
+ ProtocolV2Parser parser = new ProtocolV2Parser(transferConfig);
+ FetchV2Request req = parser.parseFetchRequest(pckIn);
+ currentRequest = req;
+ Map<String, ObjectId> wantedRefs = wantedRefs(req);
+
// Depending on the requestValidator, #processHaveLines may
// require that advertised be set. Set it only in the required
// circumstances (to avoid a full ref lookup in the case that
@@ -1184,15 +1175,25 @@ public class UploadPack implements Closeable {
requestValidator instanceof AnyRequestValidator) {
advertised = Collections.emptySet();
} else {
- advertised = refIdSet(getAdvertisedOrDefaultRefs().values());
+ if (req.wantIds.isEmpty()) {
+ // Only refs-in-wants in request. These ref-in-wants where used as
+ // filters already in the ls-refs, there is no need to use a full
+ // advertisement now in fetch. This improves performance and also
+ // accuracy: when the ref db prioritize and truncates the returned
+ // refs (e.g. Gerrit hides too old refs), applying a filter can
+ // return different results than a plain listing.
+ advertised = refIdSet(getFilteredRefs(wantedRefs.keySet()).values());
+ } else {
+ // At least one SHA1 in wants, so we need to take the full
+ // advertisement as base for a reachability check.
+ advertised = refIdSet(getAdvertisedOrDefaultRefs().values());
+ }
}
PackStatistics.Accumulator accumulator = new PackStatistics.Accumulator();
Instant negotiateStart = Instant.now();
+ accumulator.advertised = advertised.size();
- ProtocolV2Parser parser = new ProtocolV2Parser(transferConfig);
- FetchV2Request req = parser.parseFetchRequest(pckIn);
- currentRequest = req;
rawOut.stopBuffering();
protocolV2Hook.onFetch(req);
@@ -1205,10 +1206,10 @@ public class UploadPack implements Closeable {
// copying data back to class fields
List<ObjectId> deepenNots = parseDeepenNots(req.getDeepenNots());
- Map<String, ObjectId> wantedRefs = wantedRefs(req);
// TODO(ifrade): Avoid mutating the parsed request.
req.getWantIds().addAll(wantedRefs.values());
wantIds = req.getWantIds();
+ accumulator.wants = wantIds.size();
boolean sectionSent = false;
boolean mayHaveShallow = req.getDepth() != 0
@@ -1393,6 +1394,7 @@ public class UploadPack implements Closeable {
if (transferConfig.isAdvertiseObjectInfo()) {
caps.add(COMMAND_OBJECT_INFO);
}
+ caps.add(OPTION_AGENT + "=" + UserAgent.get());
return caps;
}
@@ -1599,13 +1601,9 @@ public class UploadPack implements Closeable {
if (!biDirectionalPipe)
adv.advertiseCapability(OPTION_NO_DONE);
RequestPolicy policy = getRequestPolicy();
- if (policy == RequestPolicy.TIP
- || policy == RequestPolicy.REACHABLE_COMMIT_TIP
- || policy == null)
+ if (policy == null || policy.implies(RequestPolicy.TIP))
adv.advertiseCapability(OPTION_ALLOW_TIP_SHA1_IN_WANT);
- if (policy == RequestPolicy.REACHABLE_COMMIT
- || policy == RequestPolicy.REACHABLE_COMMIT_TIP
- || policy == null)
+ if (policy == null || policy.implies(RequestPolicy.REACHABLE_COMMIT))
adv.advertiseCapability(OPTION_ALLOW_REACHABLE_SHA1_IN_WANT);
adv.advertiseCapability(OPTION_AGENT, UserAgent.get());
if (transferConfig.isAllowFilter()) {
@@ -1663,18 +1661,6 @@ public class UploadPack implements Closeable {
}
/**
- * Deprecated synonym for {@code getFilterSpec().getBlobLimit()}.
- *
- * @return filter blob limit requested by the client, or -1 if no limit
- * @since 5.3
- * @deprecated Use {@link #getFilterSpec()} instead
- */
- @Deprecated
- public final long getFilterBlobLimit() {
- return getFilterSpec().getBlobLimit();
- }
-
- /**
* Returns the filter spec for the current request. Valid only after
* calling recvWants(). This may be a no-op filter spec, but it won't be
* null.
@@ -1766,7 +1752,6 @@ public class UploadPack implements Closeable {
&& line.length() == PACKET_HAVE.length() + 40) {
peerHas.add(ObjectId
.fromString(line.substring(PACKET_HAVE.length())));
- accumulator.haves++;
} else if (line.equals(PACKET_DONE)) {
last = processHaveLines(peerHas, last, pckOut, accumulator, Option.NONE);
@@ -1798,6 +1783,7 @@ public class UploadPack implements Closeable {
parseWants(accumulator);
if (peerHas.isEmpty())
return last;
+ accumulator.haves += peerHas.size();
sentReady = false;
int haveCnt = 0;
@@ -1966,10 +1952,9 @@ public class UploadPack implements Closeable {
@Override
public void checkWants(UploadPack up, List<ObjectId> wants)
throws PackProtocolException, IOException {
- if (!up.isBiDirectionalPipe())
+ if (!up.isBiDirectionalPipe() || !wants.isEmpty()) {
new ReachableCommitRequestValidator().checkWants(up, wants);
- else if (!wants.isEmpty())
- throw new WantNotValidException(wants.iterator().next());
+ }
}
}
@@ -2241,7 +2226,7 @@ public class UploadPack implements Closeable {
walk.resetRetain(SAVE);
walk.markStart((RevCommit) want);
if (oldestTime != 0)
- walk.setRevFilter(CommitTimeRevFilter.after(oldestTime * 1000L));
+ walk.setRevFilter(CommitTimeRevFilter.after(Instant.ofEpochSecond(oldestTime)));
for (;;) {
final RevCommit c = walk.next();
if (c == null)
@@ -2264,7 +2249,8 @@ public class UploadPack implements Closeable {
* request in process
* @param allTags
* refs to search for annotated tags to include in the pack if
- * the {@link #OPTION_INCLUDE_TAG} capability was requested.
+ * the {@link GitProtocolConstants#OPTION_INCLUDE_TAG} capability
+ * was requested.
* @param unshallowCommits
* shallow commits on the client that are now becoming unshallow
* @param deepenNots
@@ -2325,7 +2311,8 @@ public class UploadPack implements Closeable {
* where to write statistics about the content of the pack.
* @param allTags
* refs to search for annotated tags to include in the pack if
- * the {@link #OPTION_INCLUDE_TAG} capability was requested.
+ * the {@link GitProtocolConstants#OPTION_INCLUDE_TAG} capability
+ * was requested.
* @param unshallowCommits
* shallow commits on the client that are now becoming unshallow
* @param deepenNots
@@ -2345,11 +2332,6 @@ public class UploadPack implements Closeable {
}
msgOut.flush();
- // Advertised objects and refs are not used from here on and can be
- // cleared.
- advertised = null;
- refs = null;
-
PackConfig cfg = packConfig;
if (cfg == null)
cfg = new PackConfig(db);
@@ -2391,13 +2373,19 @@ public class UploadPack implements Closeable {
pw.setTagTargets(tagTargets);
}
+ // Advertised objects and refs are not used from here on and can be
+ // cleared.
+ advertised = null;
+ refs = null;
+
RevWalk rw = walk;
if (req.getDepth() > 0 || req.getDeepenSince() != 0 || !deepenNots.isEmpty()) {
int walkDepth = req.getDepth() == 0 ? Integer.MAX_VALUE
: req.getDepth() - 1;
pw.setShallowPack(req.getDepth(), unshallowCommits);
- // Ownership is transferred below
+ // dw borrows the reader from walk which is closed by #close
+ @SuppressWarnings("resource")
DepthWalk.RevWalk dw = new DepthWalk.RevWalk(
walk.getObjectReader(), walkDepth);
dw.setDeepenSince(req.getDeepenSince());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java
index df98d0cfd5..b23ee97dcb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java
@@ -10,10 +10,6 @@
package org.eclipse.jgit.transport;
-import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
-
-import java.util.Set;
-
import org.eclipse.jgit.util.StringUtils;
/**
@@ -91,40 +87,6 @@ public class UserAgent {
userAgent = StringUtils.isEmptyOrNull(agent) ? null : clean(agent);
}
- /**
- *
- * @param options
- * @param transportAgent
- * @return The transport agent.
- * @deprecated Capabilities with <key>=<value> shape are now parsed
- * alongside other capabilities in the ReceivePack flow.
- */
- @Deprecated
- static String getAgent(Set<String> options, String transportAgent) {
- if (options == null || options.isEmpty()) {
- return transportAgent;
- }
- for (String o : options) {
- if (o.startsWith(OPTION_AGENT)
- && o.length() > OPTION_AGENT.length()
- && o.charAt(OPTION_AGENT.length()) == '=') {
- return o.substring(OPTION_AGENT.length() + 1);
- }
- }
- return transportAgent;
- }
-
- /**
- *
- * @param options
- * @return True if the transport agent is set. False otherwise.
- * @deprecated Capabilities with <key>=<value> shape are now parsed
- * alongside other capabilities in the ReceivePack flow.
- */
- @Deprecated
- static boolean hasAgent(Set<String> options) {
- return getAgent(options, null) != null;
- }
private UserAgent() {
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UsernamePasswordCredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UsernamePasswordCredentialsProvider.java
index c0de42cb57..da8af5a0fe 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UsernamePasswordCredentialsProvider.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UsernamePasswordCredentialsProvider.java
@@ -48,13 +48,11 @@ public class UsernamePasswordCredentialsProvider extends CredentialsProvider {
this.password = password;
}
- /** {@inheritDoc} */
@Override
public boolean isInteractive() {
return false;
}
- /** {@inheritDoc} */
@Override
public boolean supports(CredentialItem... items) {
for (CredentialItem i : items) {
@@ -77,7 +75,6 @@ public class UsernamePasswordCredentialsProvider extends CredentialsProvider {
return true;
}
- /** {@inheritDoc} */
@Override
public boolean get(URIish uri, CredentialItem... items)
throws UnsupportedCredentialItem {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java
index c8cdb5a549..3bfc5234ee 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java
@@ -130,6 +130,7 @@ abstract class WalkEncryption {
* EncryptionUtil.java</a>
* <p>
* Note: EncryptionUtil is inadequate:
+ * <ul>
* <li>EncryptionUtil.isCipherAvailableForUse checks encryption only which
* "always works", but in JetS3t both encryption and decryption use non-IV
* aware algorithm parameters for all PBE specs, which breaks in case of AES
@@ -137,6 +138,7 @@ abstract class WalkEncryption {
* JetS3t, such as PBEWithMD5AndDES and PBEWithSHAAndTwofish-CBC
* <li>any AES based algorithms such as "PBE...With...And...AES" will not
* work, since they need proper IV setup
+ * </ul>
*/
static class JetS3tV2 extends WalkEncryption {
@@ -516,8 +518,10 @@ abstract class WalkEncryption {
* Encryption factory.
*
* @param props
- * @return instance
+ * configuration properties
+ * @return instance this object
* @throws GeneralSecurityException
+ * if generic security failure occurred
*/
static WalkEncryption instance(Properties props)
throws GeneralSecurityException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
index ed8f450c53..b7bb0cbce3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
@@ -16,13 +16,16 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.MessageFormat;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.jgit.errors.CompoundException;
@@ -112,16 +115,16 @@ class WalkFetchConnection extends BaseFetchConnection {
private final DateRevQueue localCommitQueue;
/** Objects we need to copy from the remote repository. */
- private LinkedList<ObjectId> workQueue;
+ private Deque<ObjectId> workQueue;
/** Databases we have not yet obtained the list of packs from. */
- private final LinkedList<WalkRemoteObjectDatabase> noPacksYet;
+ private final Deque<WalkRemoteObjectDatabase> noPacksYet;
/** Databases we have not yet obtained the alternates from. */
- private final LinkedList<WalkRemoteObjectDatabase> noAlternatesYet;
+ private final Deque<WalkRemoteObjectDatabase> noAlternatesYet;
/** Packs we have discovered, but have not yet fetched locally. */
- private final LinkedList<RemotePack> unfetchedPacks;
+ private final Map<String, RemotePack> unfetchedPacks;
/**
* Packs whose indexes we have looked at in {@link #unfetchedPacks}.
@@ -163,13 +166,13 @@ class WalkFetchConnection extends BaseFetchConnection {
remotes = new ArrayList<>();
remotes.add(w);
- unfetchedPacks = new LinkedList<>();
+ unfetchedPacks = new LinkedHashMap<>();
packsConsidered = new HashSet<>();
- noPacksYet = new LinkedList<>();
+ noPacksYet = new ArrayDeque<>();
noPacksYet.add(w);
- noAlternatesYet = new LinkedList<>();
+ noAlternatesYet = new ArrayDeque<>();
noAlternatesYet.add(w);
fetchErrors = new HashMap<>();
@@ -183,16 +186,14 @@ class WalkFetchConnection extends BaseFetchConnection {
LOCALLY_SEEN = revWalk.newFlag("LOCALLY_SEEN"); //$NON-NLS-1$
localCommitQueue = new DateRevQueue();
- workQueue = new LinkedList<>();
+ workQueue = new ArrayDeque<>();
}
- /** {@inheritDoc} */
@Override
public boolean didFetchTestConnectivity() {
return true;
}
- /** {@inheritDoc} */
@Override
protected void doFetch(final ProgressMonitor monitor,
final Collection<Ref> want, final Set<ObjectId> have)
@@ -214,24 +215,21 @@ class WalkFetchConnection extends BaseFetchConnection {
}
}
- /** {@inheritDoc} */
@Override
public Collection<PackLock> getPackLocks() {
return packLocks;
}
- /** {@inheritDoc} */
@Override
public void setPackLockMessage(String message) {
lockMessage = message;
}
- /** {@inheritDoc} */
@Override
public void close() {
inserter.close();
reader.close();
- for (RemotePack p : unfetchedPacks) {
+ for (RemotePack p : unfetchedPacks.values()) {
if (p.tmpIdx != null)
p.tmpIdx.delete();
}
@@ -426,8 +424,9 @@ class WalkFetchConnection extends BaseFetchConnection {
if (packNameList == null || packNameList.isEmpty())
continue;
for (String packName : packNameList) {
- if (packsConsidered.add(packName))
- unfetchedPacks.add(new RemotePack(wrr, packName));
+ if (packsConsidered.add(packName)) {
+ unfetchedPacks.put(packName, new RemotePack(wrr, packName));
+ }
}
if (downloadPackedObject(pm, id))
return;
@@ -470,15 +469,27 @@ class WalkFetchConnection extends BaseFetchConnection {
}
}
+ private boolean downloadPackedObject(ProgressMonitor monitor,
+ AnyObjectId id) throws TransportException {
+ Set<String> brokenPacks = new HashSet<>();
+ try {
+ return downloadPackedObject(monitor, id, brokenPacks);
+ } finally {
+ brokenPacks.forEach(unfetchedPacks::remove);
+ }
+ }
+
@SuppressWarnings("Finally")
private boolean downloadPackedObject(final ProgressMonitor monitor,
- final AnyObjectId id) throws TransportException {
+ final AnyObjectId id, Set<String> brokenPacks) throws TransportException {
// Search for the object in a remote pack whose index we have,
// but whose pack we do not yet have.
//
- final Iterator<RemotePack> packItr = unfetchedPacks.iterator();
- while (packItr.hasNext() && !monitor.isCancelled()) {
- final RemotePack pack = packItr.next();
+ for (Entry<String, RemotePack> entry : unfetchedPacks.entrySet()) {
+ if (monitor.isCancelled()) {
+ break;
+ }
+ final RemotePack pack = entry.getValue();
try {
pack.openIndex(monitor);
} catch (IOException err) {
@@ -488,7 +499,7 @@ class WalkFetchConnection extends BaseFetchConnection {
// another source, so don't consider it a failure.
//
recordError(id, err);
- packItr.remove();
+ brokenPacks.add(entry.getKey());
continue;
}
@@ -530,15 +541,16 @@ class WalkFetchConnection extends BaseFetchConnection {
// are unusable and we shouldn't consult them again.
//
try {
- if (pack.tmpIdx != null)
+ if (pack.tmpIdx != null) {
FileUtils.delete(pack.tmpIdx);
- } catch (IOException e) {
+ }
+ } catch (Throwable e) {
if (e1 != null) {
e.addSuppressed(e1);
}
throw new TransportException(e.getMessage(), e);
}
- packItr.remove();
+ brokenPacks.add(entry.getKey());
}
if (!alreadyHave(id)) {
@@ -553,11 +565,9 @@ class WalkFetchConnection extends BaseFetchConnection {
// Complete any other objects that we can.
//
- final Iterator<ObjectId> pending = swapFetchQueue();
- while (pending.hasNext()) {
- final ObjectId p = pending.next();
+ final Deque<ObjectId> pending = swapFetchQueue();
+ for (ObjectId p : pending) {
if (pack.index.hasObject(p)) {
- pending.remove();
process(p);
} else {
workQueue.add(p);
@@ -569,9 +579,9 @@ class WalkFetchConnection extends BaseFetchConnection {
return false;
}
- private Iterator<ObjectId> swapFetchQueue() {
- final Iterator<ObjectId> r = workQueue.iterator();
- workQueue = new LinkedList<>();
+ private Deque<ObjectId> swapFetchQueue() {
+ final Deque<ObjectId> r = workQueue;
+ workQueue = new ArrayDeque<>();
return r;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
index a54fd8e14d..464017a84d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkPushConnection.java
@@ -104,7 +104,6 @@ class WalkPushConnection extends BaseConnection implements PushConnection {
dest = w;
}
- /** {@inheritDoc} */
@Override
public void push(final ProgressMonitor monitor,
final Map<String, RemoteRefUpdate> refUpdates)
@@ -112,7 +111,6 @@ class WalkPushConnection extends BaseConnection implements PushConnection {
push(monitor, refUpdates, null);
}
- /** {@inheritDoc} */
@Override
public void push(final ProgressMonitor monitor,
final Map<String, RemoteRefUpdate> refUpdates, OutputStream out)
@@ -184,7 +182,6 @@ class WalkPushConnection extends BaseConnection implements PushConnection {
}
}
- /** {@inheritDoc} */
@Override
public void close() {
dest.close();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
index 98c231a46d..95b8221a8b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnection.java
@@ -36,29 +36,39 @@ import org.eclipse.jgit.annotations.NonNull;
*/
public interface HttpConnection {
/**
+ * HttpURLConnection#HTTP_OK
+ *
* @see HttpURLConnection#HTTP_OK
*/
int HTTP_OK = java.net.HttpURLConnection.HTTP_OK;
/**
+ * HttpURLConnection#HTTP_NOT_AUTHORITATIVE
+ *
* @see HttpURLConnection#HTTP_NOT_AUTHORITATIVE
* @since 5.8
*/
int HTTP_NOT_AUTHORITATIVE = java.net.HttpURLConnection.HTTP_NOT_AUTHORITATIVE;
/**
+ * HttpURLConnection#HTTP_MOVED_PERM
+ *
* @see HttpURLConnection#HTTP_MOVED_PERM
* @since 4.7
*/
int HTTP_MOVED_PERM = java.net.HttpURLConnection.HTTP_MOVED_PERM;
/**
+ * HttpURLConnection#HTTP_MOVED_TEMP
+ *
* @see HttpURLConnection#HTTP_MOVED_TEMP
* @since 4.9
*/
int HTTP_MOVED_TEMP = java.net.HttpURLConnection.HTTP_MOVED_TEMP;
/**
+ * HttpURLConnection#HTTP_SEE_OTHER
+ *
* @see HttpURLConnection#HTTP_SEE_OTHER
* @since 4.9
*/
@@ -85,16 +95,22 @@ public interface HttpConnection {
int HTTP_11_MOVED_PERM = 308;
/**
+ * HttpURLConnection#HTTP_NOT_FOUND
+ *
* @see HttpURLConnection#HTTP_NOT_FOUND
*/
int HTTP_NOT_FOUND = java.net.HttpURLConnection.HTTP_NOT_FOUND;
/**
+ * HttpURLConnection#HTTP_UNAUTHORIZED
+ *
* @see HttpURLConnection#HTTP_UNAUTHORIZED
*/
int HTTP_UNAUTHORIZED = java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
/**
+ * HttpURLConnection#HTTP_FORBIDDEN
+ *
* @see HttpURLConnection#HTTP_FORBIDDEN
*/
int HTTP_FORBIDDEN = java.net.HttpURLConnection.HTTP_FORBIDDEN;
@@ -105,6 +121,7 @@ public interface HttpConnection {
* @see HttpURLConnection#getResponseCode()
* @return the HTTP Status-Code, or -1
* @throws java.io.IOException
+ * if an IO error occurred
*/
int getResponseCode() throws IOException;
@@ -122,6 +139,7 @@ public interface HttpConnection {
* @see HttpURLConnection#getResponseMessage()
* @return the HTTP response message, or <code>null</code>
* @throws java.io.IOException
+ * if an IO error occurred
*/
String getResponseMessage() throws IOException;
@@ -207,14 +225,14 @@ public interface HttpConnection {
* @exception IOException
* if an I/O error occurs while creating the input stream.
* @throws java.io.IOException
- * if any.
+ * if an IO error occurred
*/
InputStream getInputStream() throws IOException;
/**
* Get header field. According to
- * {@link <a href="https://tools.ietf.org/html/rfc2616#section-4.2">RFC
- * 2616</a>} header field names are case insensitive. Header fields defined
+ * <a href="https://tools.ietf.org/html/rfc2616#section-4.2">RFC 2616</a>
+ * header field names are case insensitive. Header fields defined
* as a comma separated list can have multiple header fields with the same
* field name. This method only returns one of these header fields. If you
* want the union of all values of all multiple header fields with the same
@@ -230,8 +248,8 @@ public interface HttpConnection {
/**
* Get all values of given header field. According to
- * {@link <a href="https://tools.ietf.org/html/rfc2616#section-4.2">RFC
- * 2616</a>} header field names are case insensitive. Header fields defined
+ * <a href="https://tools.ietf.org/html/rfc2616#section-4.2">RFC 2616</a>
+ * header field names are case insensitive. Header fields defined
* as a comma separated list can have multiple header fields with the same
* field name. This method does not validate if the given header field is
* defined as a comma separated list.
@@ -287,6 +305,7 @@ public interface HttpConnection {
* @see HttpURLConnection#getOutputStream()
* @return an output stream that writes to this connection.
* @throws java.io.IOException
+ * if an IO error occurred
*/
OutputStream getOutputStream() throws IOException;
@@ -321,6 +340,7 @@ public interface HttpConnection {
*
* @see HttpURLConnection#connect()
* @throws java.io.IOException
+ * if an IO error occurred
*/
void connect() throws IOException;
@@ -338,7 +358,9 @@ public interface HttpConnection {
* the source of randomness for this generator or null. See
* {@link javax.net.ssl.SSLContext#init(KeyManager[], TrustManager[], SecureRandom)}
* @throws java.security.NoSuchAlgorithmException
+ * if algorithm isn't available
* @throws java.security.KeyManagementException
+ * if key management failed
*/
void configure(KeyManager[] km, TrustManager[] tm,
SecureRandom random) throws NoSuchAlgorithmException,
@@ -351,7 +373,9 @@ public interface HttpConnection {
* @param hostnameverifier
* a {@link javax.net.ssl.HostnameVerifier} object.
* @throws java.security.NoSuchAlgorithmException
+ * if algorithm isn't available
* @throws java.security.KeyManagementException
+ * if key management failed
*/
void setHostnameVerifier(HostnameVerifier hostnameverifier)
throws NoSuchAlgorithmException, KeyManagementException;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java
index bd0aa3f7ad..b50eaa4186 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory.java
@@ -28,6 +28,7 @@ public interface HttpConnectionFactory {
* a {@link java.net.URL} object.
* @return a {@link org.eclipse.jgit.transport.http.HttpConnection}
* @throws java.io.IOException
+ * if an IO error occurred
*/
HttpConnection create(URL url) throws IOException;
@@ -41,6 +42,7 @@ public interface HttpConnectionFactory {
* the proxy to be used
* @return a {@link org.eclipse.jgit.transport.http.HttpConnection}
* @throws java.io.IOException
+ * if an IO error occurred
*/
HttpConnection create(URL url, Proxy proxy)
throws IOException;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory2.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory2.java
index 88abc60162..71d5c1b6f7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory2.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/HttpConnectionFactory2.java
@@ -48,7 +48,7 @@ public interface HttpConnectionFactory2 extends HttpConnectionFactory {
* {@link HttpConnectionFactory} instance
* @param sslVerify
* whether SSL is to be verified
- * @return the configured {@connection}
+ * @return the configured {@code connection}
* @throws IOException
* if the connection cannot be configured
* @throws GeneralSecurityException
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java
index 3b0bae21ef..e20acadc4a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/http/JDKHttpConnection.java
@@ -20,7 +20,7 @@ import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -55,7 +55,9 @@ public class JDKHttpConnection implements HttpConnection {
* @param url
* a {@link java.net.URL} object.
* @throws java.net.MalformedURLException
+ * if URL is malformed
* @throws java.io.IOException
+ * if an IO error occurred
*/
protected JDKHttpConnection(URL url)
throws MalformedURLException,
@@ -71,7 +73,9 @@ public class JDKHttpConnection implements HttpConnection {
* @param proxy
* a {@link java.net.Proxy} object.
* @throws java.net.MalformedURLException
+ * if URL is malformed
* @throws java.io.IOException
+ * if an IO error occurred
*/
protected JDKHttpConnection(URL url, Proxy proxy)
throws MalformedURLException, IOException {
@@ -79,73 +83,61 @@ public class JDKHttpConnection implements HttpConnection {
.openConnection(proxy);
}
- /** {@inheritDoc} */
@Override
public int getResponseCode() throws IOException {
return wrappedUrlConnection.getResponseCode();
}
- /** {@inheritDoc} */
@Override
public URL getURL() {
return wrappedUrlConnection.getURL();
}
- /** {@inheritDoc} */
@Override
public String getResponseMessage() throws IOException {
return wrappedUrlConnection.getResponseMessage();
}
- /** {@inheritDoc} */
@Override
public Map<String, List<String>> getHeaderFields() {
return wrappedUrlConnection.getHeaderFields();
}
- /** {@inheritDoc} */
@Override
public void setRequestProperty(String key, String value) {
wrappedUrlConnection.setRequestProperty(key, value);
}
- /** {@inheritDoc} */
@Override
public void setRequestMethod(String method) throws ProtocolException {
wrappedUrlConnection.setRequestMethod(method);
}
- /** {@inheritDoc} */
@Override
public void setUseCaches(boolean usecaches) {
wrappedUrlConnection.setUseCaches(usecaches);
}
- /** {@inheritDoc} */
@Override
public void setConnectTimeout(int timeout) {
wrappedUrlConnection.setConnectTimeout(timeout);
}
- /** {@inheritDoc} */
@Override
public void setReadTimeout(int timeout) {
wrappedUrlConnection.setReadTimeout(timeout);
}
- /** {@inheritDoc} */
@Override
public String getContentType() {
return wrappedUrlConnection.getContentType();
}
- /** {@inheritDoc} */
@Override
public InputStream getInputStream() throws IOException {
return wrappedUrlConnection.getInputStream();
}
- /** {@inheritDoc} */
@Override
public String getHeaderField(@NonNull String name) {
return wrappedUrlConnection.getHeaderField(name);
@@ -160,75 +152,64 @@ public class JDKHttpConnection implements HttpConnection {
private static List<String> mapValuesToListIgnoreCase(String keyName,
Map<String, List<String>> m) {
- List<String> fields = new LinkedList<>();
+ List<String> fields = new ArrayList<>();
m.entrySet().stream().filter(e -> keyName.equalsIgnoreCase(e.getKey()))
.filter(e -> e.getValue() != null)
.forEach(e -> fields.addAll(e.getValue()));
return fields;
}
- /** {@inheritDoc} */
@Override
public int getContentLength() {
return wrappedUrlConnection.getContentLength();
}
- /** {@inheritDoc} */
@Override
public void setInstanceFollowRedirects(boolean followRedirects) {
wrappedUrlConnection.setInstanceFollowRedirects(followRedirects);
}
- /** {@inheritDoc} */
@Override
public void setDoOutput(boolean dooutput) {
wrappedUrlConnection.setDoOutput(dooutput);
}
- /** {@inheritDoc} */
@Override
public void setFixedLengthStreamingMode(int contentLength) {
wrappedUrlConnection.setFixedLengthStreamingMode(contentLength);
}
- /** {@inheritDoc} */
@Override
public OutputStream getOutputStream() throws IOException {
return wrappedUrlConnection.getOutputStream();
}
- /** {@inheritDoc} */
@Override
public void setChunkedStreamingMode(int chunklen) {
wrappedUrlConnection.setChunkedStreamingMode(chunklen);
}
- /** {@inheritDoc} */
@Override
public String getRequestMethod() {
return wrappedUrlConnection.getRequestMethod();
}
- /** {@inheritDoc} */
@Override
public boolean usingProxy() {
return wrappedUrlConnection.usingProxy();
}
- /** {@inheritDoc} */
@Override
public void connect() throws IOException {
wrappedUrlConnection.connect();
}
- /** {@inheritDoc} */
@Override
public void setHostnameVerifier(HostnameVerifier hostnameverifier) {
((HttpsURLConnection) wrappedUrlConnection)
.setHostnameVerifier(hostnameverifier);
}
- /** {@inheritDoc} */
@Override
public void configure(KeyManager[] km, TrustManager[] tm,
SecureRandom random) throws NoSuchAlgorithmException,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
index 046f395049..36731a5fa6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java
@@ -60,7 +60,6 @@ public class FileResolver<C> implements RepositoryResolver<C> {
setExportAll(exportAll);
}
- /** {@inheritDoc} */
@Override
public Repository open(C req, String name)
throws RepositoryNotFoundException, ServiceNotEnabledException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
index 3ef5b29a55..65b18086b4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
@@ -718,7 +718,6 @@ public abstract class AbstractTreeIterator {
System.arraycopy(path, pathOffset, buffer, offset, pathLen - pathOffset);
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
index 5c3f6aefe1..c6d50d35f1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java
@@ -191,7 +191,6 @@ public class CanonicalTreeParser extends AbstractTreeIterator {
reset(reader.open(id, OBJ_TREE).getCachedBytes());
}
- /** {@inheritDoc} */
@Override
public CanonicalTreeParser createSubtreeIterator(final ObjectReader reader,
final MutableObjectId idBuffer)
@@ -227,51 +226,43 @@ public class CanonicalTreeParser extends AbstractTreeIterator {
return p;
}
- /** {@inheritDoc} */
@Override
public CanonicalTreeParser createSubtreeIterator(ObjectReader reader)
throws IncorrectObjectTypeException, IOException {
return createSubtreeIterator(reader, new MutableObjectId());
}
- /** {@inheritDoc} */
@Override
public boolean hasId() {
return true;
}
- /** {@inheritDoc} */
@Override
public byte[] idBuffer() {
return raw;
}
- /** {@inheritDoc} */
@Override
public int idOffset() {
return nextPtr - OBJECT_ID_LENGTH;
}
- /** {@inheritDoc} */
@Override
public void reset() {
if (!first())
reset(raw);
}
- /** {@inheritDoc} */
@Override
public boolean first() {
return currPtr == 0;
}
- /** {@inheritDoc} */
@Override
public boolean eof() {
return currPtr == raw.length;
}
- /** {@inheritDoc} */
@Override
public void next(int delta) {
if (delta == 1) {
@@ -301,7 +292,6 @@ public class CanonicalTreeParser extends AbstractTreeIterator {
parseEntry();
}
- /** {@inheritDoc} */
@Override
public void back(int delta) {
if (delta == 1 && 0 <= prevPtr) {
@@ -376,6 +366,7 @@ public class CanonicalTreeParser extends AbstractTreeIterator {
* @return {@link org.eclipse.jgit.attributes.AttributesNode} for the
* current entry.
* @throws java.io.IOException
+ * if an IO error occurred
* @since 4.2
*/
public AttributesNode getEntryAttributesNode(ObjectReader reader)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java
index 0661c9044a..32368dcfb8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java
@@ -57,75 +57,63 @@ public class EmptyTreeIterator extends AbstractTreeIterator {
pathLen = childPathOffset - 1;
}
- /** {@inheritDoc} */
@Override
public AbstractTreeIterator createSubtreeIterator(ObjectReader reader)
throws IncorrectObjectTypeException, IOException {
return new EmptyTreeIterator(this);
}
- /** {@inheritDoc} */
@Override
public boolean hasId() {
return false;
}
- /** {@inheritDoc} */
@Override
public ObjectId getEntryObjectId() {
return ObjectId.zeroId();
}
- /** {@inheritDoc} */
@Override
public byte[] idBuffer() {
return zeroid;
}
- /** {@inheritDoc} */
@Override
public int idOffset() {
return 0;
}
- /** {@inheritDoc} */
@Override
public void reset() {
// Do nothing.
}
- /** {@inheritDoc} */
@Override
public boolean first() {
return true;
}
- /** {@inheritDoc} */
@Override
public boolean eof() {
return true;
}
- /** {@inheritDoc} */
@Override
public void next(int delta) throws CorruptObjectException {
// Do nothing.
}
- /** {@inheritDoc} */
@Override
public void back(int delta) throws CorruptObjectException {
// Do nothing.
}
- /** {@inheritDoc} */
@Override
public void stopWalk() {
if (parent != null)
parent.stopWalk();
}
- /** {@inheritDoc} */
@Override
protected boolean needsStopWalk() {
return parent != null && parent.needsStopWalk();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
index 60b92d7271..0cac374844 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
@@ -175,7 +175,6 @@ public class FileTreeIterator extends WorkingTreeIterator {
init(entries());
}
- /** {@inheritDoc} */
@Override
public AbstractTreeIterator createSubtreeIterator(ObjectReader reader)
throws IncorrectObjectTypeException, IOException {
@@ -372,12 +371,6 @@ public class FileTreeIterator extends WorkingTreeIterator {
return attributes.getLength();
}
- @Override
- @Deprecated
- public long getLastModified() {
- return attributes.getLastModifiedInstant().toEpochMilli();
- }
-
/**
* @since 5.1.9
*/
@@ -425,13 +418,11 @@ public class FileTreeIterator extends WorkingTreeIterator {
return ((FileEntry) current()).getFile();
}
- /** {@inheritDoc} */
@Override
protected byte[] idSubmodule(Entry e) {
return idSubmodule(getDirectory(), e);
}
- /** {@inheritDoc} */
@Override
protected String readSymlinkTarget(Entry entry) throws IOException {
return fs.readSymLink(getEntryFile());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
index ece945232e..31c216b4a8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -38,12 +38,12 @@ import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
@@ -78,6 +78,8 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
private static final AbstractTreeIterator[] NO_TREES = {};
/**
+ * Type of operation to retrieve git attributes for.
+ *
* @since 4.2
*/
public enum OperationType {
@@ -548,7 +550,7 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
* <p>
* Retrieve the git attributes for the current entry.
*
- * <h3>Git attribute computation</h3>
+ * <h4>Git attribute computation</h4>
*
* <ul>
* <li>Get the attributes matching the current path entry from the info file
@@ -563,11 +565,10 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
* <li>In the end, completes the list of matching attributes using the
* global attribute file define in the configuration (see
* {@link AttributesNodeProvider#getGlobalAttributesNode()})</li>
- *
* </ul>
*
*
- * <h3>Iterator constraints</h3>
+ * <h4>Iterator constraints</h4>
*
* <p>
* In order to have a correct list of attributes for the current entry, this
@@ -960,6 +961,8 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
* iterators to manage only one list of items, with the diving handled by
* recursive trees.
*
+ * @param <T>
+ * Type of returned {@code AbstractTreeIterator}
* @param nth
* tree to obtain the current iterator of.
* @param clazz
@@ -1376,12 +1379,14 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
}
/**
- * Returns an AbstractTreeIterator from {@code trees} with the smallest name, and sets its
- * {@code matches} field. This may clobber {@code matches} in other {@code tree}s. Other iterators
- * at the same name will have their {@code matches} pointing to the same {@code min()} value.
+ * Returns an AbstractTreeIterator from {@code trees} with the smallest
+ * name, and sets its {@code matches} field. This may clobber
+ * {@code matches} in other {@code tree}s. Other iterators at the same name
+ * will have their {@code matches} pointing to the same {@code min()} value.
*
* @return the smallest tree iterator available.
* @throws CorruptObjectException
+ * if an object is corrupt
*/
@SuppressWarnings("unused")
AbstractTreeIterator min() throws CorruptObjectException {
@@ -1488,6 +1493,7 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
* {{@link #getSmudgeCommand(int)} instead.
* @return a filter command
* @throws java.io.IOException
+ * if an IO error occurred
* @since 4.2
*/
public String getFilterCommand(String filterCommandType)
@@ -1510,7 +1516,7 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
}
return filterCommand.replaceAll("%f", //$NON-NLS-1$
Matcher.quoteReplacement(
- QuotedString.BOURNE.quote((getPathString()))));
+ QuotedString.BOURNE.quote(getPathString())));
}
/**
@@ -1521,6 +1527,7 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
* of the tree the item to be smudged is in
* @return a filter command
* @throws java.io.IOException
+ * if an IO error occurred
* @since 6.1
*/
public String getSmudgeCommand(int index)
@@ -1536,6 +1543,7 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
* to use
* @return a filter command
* @throws java.io.IOException
+ * if an IO error occurred
* @since 6.1
*/
public String getSmudgeCommand(Attributes attributes) throws IOException {
@@ -1558,7 +1566,7 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
}
return filterCommand.replaceAll("%f", //$NON-NLS-1$
Matcher.quoteReplacement(
- QuotedString.BOURNE.quote((getPathString()))));
+ QuotedString.BOURNE.quote(getPathString())));
}
/**
@@ -1579,10 +1587,16 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
*/
private String getFilterCommandDefinition(String filterDriverName,
String filterCommandType) {
+ if (config == null) {
+ return null;
+ }
String key = filterDriverName + "." + filterCommandType; //$NON-NLS-1$
String filterCommand = filterCommandsByNameDotType.get(key);
if (filterCommand != null)
return filterCommand;
+ if (config == null) {
+ return null;
+ }
filterCommand = config.getString(ConfigConstants.CONFIG_FILTER_SECTION,
filterDriverName, filterCommandType);
boolean useBuiltin = config.getBoolean(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
index b5d6610d52..f16d800f63 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -15,7 +15,6 @@ package org.eclipse.jgit.treewalk;
import static java.nio.charset.StandardCharsets.UTF_8;
-import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -25,6 +24,7 @@ import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetEncoder;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.time.Instant;
@@ -68,8 +68,10 @@ import org.eclipse.jgit.util.Holder;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.Paths;
import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.SystemReader;
import org.eclipse.jgit.util.TemporaryBuffer;
import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;
+import org.eclipse.jgit.util.io.ByteBufferInputStream;
import org.eclipse.jgit.util.io.EolStreamTypeUtil;
import org.eclipse.jgit.util.sha1.SHA1;
@@ -270,7 +272,6 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
return state.walkIgnored;
}
- /** {@inheritDoc} */
@Override
public boolean hasId() {
if (contentIdFromPtr == ptr)
@@ -278,7 +279,6 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
return (mode & FileMode.TYPE_MASK) == FileMode.TYPE_FILE;
}
- /** {@inheritDoc} */
@Override
public byte[] idBuffer() {
if (contentIdFromPtr == ptr)
@@ -316,7 +316,6 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
return zeroid;
}
- /** {@inheritDoc} */
@Override
public boolean isWorkTree() {
return true;
@@ -408,9 +407,9 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
if (len <= MAXIMUM_FILE_SIZE_TO_READ_FULLY) {
InputStream is = e.openInputStream();
try {
- ByteBuffer rawbuf = IO.readWholeStream(is, (int) len);
- rawbuf = filterClean(rawbuf.array(), rawbuf.limit());
- return rawbuf.limit();
+ ByteBuffer filteredData = IO.readWholeStream(filterClean(is),
+ (int) len);
+ return filteredData.remaining();
} finally {
safeClose(is);
}
@@ -439,10 +438,9 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
}
if (len <= MAXIMUM_FILE_SIZE_TO_READ_FULLY) {
- ByteBuffer rawbuf = IO.readWholeStream(is, (int) len);
- rawbuf = filterClean(rawbuf.array(), rawbuf.limit());
- canonLen = rawbuf.limit();
- return new ByteArrayInputStream(rawbuf.array(), 0, (int) canonLen);
+ ByteBuffer filteredData = IO.readWholeStream(filterClean(is), (int) len);
+ canonLen = filteredData.remaining();
+ return new ByteBufferInputStream(filteredData);
}
if (getCleanFilterCommand() == null && isBinary(e)) {
@@ -478,16 +476,6 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
}
}
- private ByteBuffer filterClean(byte[] src, int n)
- throws IOException {
- InputStream in = new ByteArrayInputStream(src);
- try {
- return IO.readWholeStream(filterClean(in), n);
- } finally {
- safeClose(in);
- }
- }
-
private InputStream filterClean(InputStream in)
throws IOException {
in = EolStreamTypeUtil.wrapInputStream(in,
@@ -510,6 +498,8 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
filterProcessBuilder.directory(repository.getWorkTree());
filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY,
repository.getDirectory().getAbsolutePath());
+ filterProcessBuilder.environment().put(Constants.GIT_COMMON_DIR_KEY,
+ repository.getCommonDirectory().getAbsolutePath());
ExecutionResult result;
try {
result = fs.execute(filterProcessBuilder, in);
@@ -549,13 +539,11 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
return repository;
}
- /** {@inheritDoc} */
@Override
public int idOffset() {
return contentIdOffset;
}
- /** {@inheritDoc} */
@Override
public void reset() {
if (!first()) {
@@ -565,19 +553,16 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
}
}
- /** {@inheritDoc} */
@Override
public boolean first() {
return ptr == 0;
}
- /** {@inheritDoc} */
@Override
public boolean eof() {
return ptr == entryCnt;
}
- /** {@inheritDoc} */
@Override
public void next(int delta) throws CorruptObjectException {
ptr += delta;
@@ -586,7 +571,6 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
}
}
- /** {@inheritDoc} */
@Override
public void back(int delta) throws CorruptObjectException {
ptr -= delta;
@@ -620,6 +604,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
*
* @return size of the content, in bytes
* @throws java.io.IOException
+ * if an IO error occurred
*/
public long getEntryContentLength() throws IOException {
if (canonLen == -1) {
@@ -637,18 +622,6 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
/**
* Get the last modified time of this entry.
*
- * @return last modified time of this file, in milliseconds since the epoch
- * (Jan 1, 1970 UTC).
- * @deprecated use {@link #getEntryLastModifiedInstant()} instead
- */
- @Deprecated
- public long getEntryLastModified() {
- return current().getLastModified();
- }
-
- /**
- * Get the last modified time of this entry.
- *
* @return last modified time of this file
* @since 5.1.9
*/
@@ -772,6 +745,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
* @return the {@link org.eclipse.jgit.attributes.AttributesNode} for the
* current entry.
* @throws IOException
+ * if an IO error occurred
*/
public AttributesNode getEntryAttributesNode() throws IOException {
if (attributesNode instanceof PerDirectoryAttributesNode)
@@ -964,6 +938,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
* access to repository objects if necessary. Should not be null.
* @return true if content is most likely different.
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.3
*/
public boolean isModified(DirCacheEntry entry, boolean forceContentCheck,
@@ -1070,6 +1045,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
* @return <code>true</code> if the content doesn't match,
* <code>false</code> if it matches
* @throws IOException
+ * if an IO error occurred
*/
private boolean contentCheck(DirCacheEntry entry, ObjectReader reader)
throws IOException {
@@ -1243,21 +1219,6 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
* 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.
- * @deprecated use {@link #getLastModifiedInstant()} instead
- */
- @Deprecated
- public abstract long getLastModified();
-
- /**
- * Get the last modified time of this entry.
- * <p>
- * <b>Note: Efficient implementation required.</b>
- * <p>
- * The implementation of this method must be efficient. If a subclass
- * needs to compute the value they should cache the reference within an
- * instance member instead.
- *
* @return time of the last change.
* @since 5.1.9
*/
@@ -1334,7 +1295,11 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_EXCLUDESFILE, fs, null, null);
if (path != null) {
- loadRulesFromFile(coreExclude, path.toFile());
+ if (Files.exists(path)) {
+ loadRulesFromFile(coreExclude, path.toFile());
+ }
+ } else {
+ loadRulesFromDefaultFile(coreExclude, fs);
}
if (coreExclude.getRules().isEmpty()) {
coreExclude = parent;
@@ -1342,9 +1307,11 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
IgnoreNode infoExclude = new IgnoreNodeWithParent(
coreExclude);
- File exclude = fs.resolve(repository.getDirectory(),
+ File exclude = fs.resolve(repository.getCommonDirectory(),
Constants.INFO_EXCLUDE);
- loadRulesFromFile(infoExclude, exclude);
+ if (fs.exists(exclude)) {
+ loadRulesFromFile(infoExclude, exclude);
+ }
if (infoExclude.getRules().isEmpty()) {
infoExclude = null;
}
@@ -1366,9 +1333,19 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
private static void loadRulesFromFile(IgnoreNode r, File exclude)
throws FileNotFoundException, IOException {
- if (FS.DETECTED.exists(exclude)) {
- try (FileInputStream in = new FileInputStream(exclude)) {
- r.parse(exclude.getAbsolutePath(), in);
+ try (FileInputStream in = new FileInputStream(exclude)) {
+ r.parse(exclude.getAbsolutePath(), in);
+ }
+ }
+
+ private static void loadRulesFromDefaultFile(IgnoreNode r,
+ FS fileSystem) throws FileNotFoundException, IOException {
+ Path cfg = SystemReader.getInstance()
+ .getXdgConfigDirectory(fileSystem);
+ if (cfg != null) {
+ Path cfgPath = cfg.resolve("git").resolve("ignore"); //$NON-NLS-1$ //$NON-NLS-2$
+ if (Files.exists(cfgPath)) {
+ loadRulesFromFile(r, cfgPath.toFile());
}
}
}
@@ -1450,6 +1427,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
* @return the clean filter command for the current entry or
* <code>null</code> if no such command is defined
* @throws java.io.IOException
+ * if an IO error occurred
* @since 4.2
*/
public String getCleanFilterCommand() throws IOException {
@@ -1472,6 +1450,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
* {@link org.eclipse.jgit.treewalk.TreeWalk} is not based on a
* {@link org.eclipse.jgit.lib.Repository} then null is returned.
* @throws java.io.IOException
+ * if an IO error occurred
* @since 4.3
*/
public EolStreamType getEolStreamType() throws IOException {
@@ -1486,6 +1465,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
* {@link TreeWalk} is not based on a {@link Repository} then null
* is returned.
* @throws IOException
+ * if an IO error occurred
*/
private EolStreamType getEolStreamType(OperationType opType)
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
index c6804da039..b35dbebd17 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java
@@ -12,11 +12,14 @@
package org.eclipse.jgit.treewalk.filter;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Collection;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
/**
@@ -100,6 +103,13 @@ public abstract class AndTreeFilter extends TreeFilter {
}
@Override
+ public boolean shouldTreeWalk(RevCommit c, RevWalk rw,
+ MutableBoolean cpfUsed) {
+ return a.shouldTreeWalk(c, rw, cpfUsed)
+ && b.shouldTreeWalk(c, rw, cpfUsed);
+ }
+
+ @Override
public int matchFilter(TreeWalk walker)
throws MissingObjectException, IncorrectObjectTypeException,
IOException {
@@ -174,6 +184,13 @@ public abstract class AndTreeFilter extends TreeFilter {
}
@Override
+ public boolean shouldTreeWalk(RevCommit c, RevWalk rw,
+ MutableBoolean cpfUsed) {
+ return Arrays.stream(subfilters)
+ .allMatch(t -> t.shouldTreeWalk(c, rw, cpfUsed));
+ }
+
+ @Override
public TreeFilter clone() {
final TreeFilter[] s = new TreeFilter[subfilters.length];
for (int i = 0; i < s.length; i++)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ByteArraySet.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ByteArraySet.java
index cafa926ffc..33db6ea661 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ByteArraySet.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ByteArraySet.java
@@ -13,6 +13,10 @@
package org.eclipse.jgit.treewalk.filter;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+
import org.eclipse.jgit.util.RawParseUtils;
/**
@@ -39,6 +43,7 @@ class ByteArraySet {
* Create an empty set.
*
* @param capacity
+ * initial capacity of the set
*/
ByteArraySet(int capacity) {
initTable(1 << Integer.highestOneBit((capacity * 2) - 1));
@@ -136,13 +141,19 @@ class ByteArraySet {
}
/**
+ * Returns number of arrays in the set
+ *
* @return number of arrays in the set
*/
int size() {
return size;
}
- /** @return true if {@link #size()} is 0. */
+ /**
+ * Returns true if {@link #size()} is 0
+ *
+ * @return true if {@link #size()} is 0
+ */
boolean isEmpty() {
return size == 0;
}
@@ -180,7 +191,6 @@ class ByteArraySet {
table = new byte[sz][];
}
- /** {@inheritDoc} */
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@@ -291,4 +301,8 @@ class ByteArraySet {
return ret;
}
+ Set<byte[]> toSet() {
+ return Arrays.stream(toArray()).collect(Collectors.toSet());
+ }
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ChangedPathTreeFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ChangedPathTreeFilter.java
new file mode 100644
index 0000000000..a74b9b617f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/ChangedPathTreeFilter.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2025, Google LLC and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.treewalk.filter;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.commitgraph.ChangedPathFilter;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.util.StringUtils;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Filter tree entries that modified the contents of particular file paths.
+ * <p>
+ * Equivalent to AndTreeFilter(PathFilter, AnyDiffFilter). This filter uses
+ * {@link org.eclipse.jgit.internal.storage.commitgraph.ChangedPathFilter}
+ * (bloom filters) when available to discard commits without diffing their
+ * trees.
+ *
+ * @since 7.3
+ */
+public class ChangedPathTreeFilter extends TreeFilter {
+
+ private TreeFilter pathFilter;
+
+ private List<String> paths;
+
+ private List<byte[]> rawPaths;
+
+ /**
+ * Create a TreeFilter for trees modifying one or more user supplied paths.
+ * <p>
+ * Path strings are relative to the root of the repository. If the user's
+ * input should be assumed relative to a subdirectory of the repository the
+ * caller must prepend the subdirectory's path prior to creating the filter.
+ * <p>
+ * Path strings use '/' to delimit directories on all platforms.
+ * <p>
+ * Paths may appear in any order within the collection. Sorting may be done
+ * internally when the group is constructed if doing so will improve path
+ * matching performance.
+ *
+ * @param paths
+ * the paths to test against. Must have at least one entry.
+ * @return a new filter for the list of paths supplied.
+ */
+ public static ChangedPathTreeFilter create(String... paths) {
+ return new ChangedPathTreeFilter(paths);
+ }
+
+ private ChangedPathTreeFilter(String... paths) {
+ List<String> filtered = Arrays.stream(paths)
+ .map(s -> StringUtils.trim(s, '/'))
+ .collect(Collectors.toList());
+
+ if (filtered.size() == 0)
+ throw new IllegalArgumentException(
+ JGitText.get().atLeastOnePathIsRequired);
+
+ if (filtered.stream().anyMatch(s -> s.isEmpty() || s.isBlank())) {
+ throw new IllegalArgumentException(
+ JGitText.get().emptyPathNotPermitted);
+ }
+
+ this.paths = filtered;
+ this.rawPaths = this.paths.stream().map(Constants::encode)
+ .collect(Collectors.toList());
+ if (filtered.size() == 1) {
+ this.pathFilter = PathFilter.create(paths[0]);
+ } else {
+ this.pathFilter = OrTreeFilter.create(Arrays.stream(paths)
+ .map(PathFilter::create).collect(Collectors.toList()));
+ }
+ }
+
+ @Override
+ public boolean shouldTreeWalk(RevCommit c, RevWalk rw,
+ MutableBoolean cpfUsed) {
+ ChangedPathFilter cpf = c.getChangedPathFilter(rw);
+ if (cpf == null) {
+ return true;
+ }
+ if (cpfUsed != null) {
+ cpfUsed.orValue(true);
+ }
+ // return true if at least one path might exist in cpf
+ return rawPaths.stream().anyMatch(cpf::maybeContains);
+ }
+
+ @Override
+ public boolean include(TreeWalk walker) throws IOException {
+ return pathFilter.include(walker) && ANY_DIFF.include(walker);
+ }
+
+ @Override
+ public boolean shouldBeRecursive() {
+ return pathFilter.shouldBeRecursive() || ANY_DIFF.shouldBeRecursive();
+ }
+
+ @Override
+ public ChangedPathTreeFilter clone() {
+ return this;
+ }
+
+ /**
+ * Get the paths this filter matches.
+ *
+ * @return the paths this filter matches.
+ */
+ public List<String> getPaths() {
+ return paths;
+ }
+
+ @SuppressWarnings("nls")
+ @Override
+ public String toString() {
+ return "(CHANGED_PATH(" + pathFilter.toString() + ")" //
+ + " AND " //
+ + ANY_DIFF.toString() + ")";
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java
index 4731f345bc..cfdc4dd358 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java
@@ -10,8 +10,9 @@
package org.eclipse.jgit.treewalk.filter;
import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -62,9 +63,9 @@ public class IndexDiffFilter extends TreeFilter {
private final Set<String> ignoredPaths = new HashSet<>();
- private final LinkedList<String> untrackedParentFolders = new LinkedList<>();
+ private final ArrayDeque<String> untrackedParentFolders = new ArrayDeque<>();
- private final LinkedList<String> untrackedFolders = new LinkedList<>();
+ private final ArrayDeque<String> untrackedFolders = new ArrayDeque<>();
/**
* Creates a new instance of this filter. Do not use an instance of this
@@ -106,7 +107,6 @@ public class IndexDiffFilter extends TreeFilter {
this.honorIgnores = honorIgnores;
}
- /** {@inheritDoc} */
@Override
public boolean include(TreeWalk tw) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
@@ -234,7 +234,6 @@ public class IndexDiffFilter extends TreeFilter {
return tw.getTree(workingTree, WorkingTreeIterator.class);
}
- /** {@inheritDoc} */
@Override
public boolean shouldBeRecursive() {
// We cannot compare subtrees in the working tree, so encourage
@@ -242,13 +241,11 @@ public class IndexDiffFilter extends TreeFilter {
return true;
}
- /** {@inheritDoc} */
@Override
public TreeFilter clone() {
return this;
}
- /** {@inheritDoc} */
@Override
public String toString() {
return "INDEX_DIFF_FILTER"; //$NON-NLS-1$
@@ -276,12 +273,14 @@ public class IndexDiffFilter extends TreeFilter {
* empty list will be returned.
*/
public List<String> getUntrackedFolders() {
- LinkedList<String> ret = new LinkedList<>(untrackedFolders);
+ ArrayList<String> ret = new ArrayList<>(untrackedFolders);
if (!untrackedParentFolders.isEmpty()) {
String toBeAdded = untrackedParentFolders.getLast();
- while (!ret.isEmpty() && ret.getLast().startsWith(toBeAdded))
- ret.removeLast();
- ret.addLast(toBeAdded);
+ while (!ret.isEmpty()
+ && ret.get(ret.size() - 1).startsWith(toBeAdded)) {
+ ret.remove(ret.size() - 1);
+ }
+ ret.add(toBeAdded);
}
return ret;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java
index 163dc71daa..0c0b09e7bf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/InterIndexDiffFilter.java
@@ -28,7 +28,6 @@ public final class InterIndexDiffFilter extends TreeFilter {
*/
public static final TreeFilter INSTANCE = new InterIndexDiffFilter();
- /** {@inheritDoc} */
@Override
public boolean include(TreeWalk walker) {
final int n = walker.getTreeCount();
@@ -57,19 +56,16 @@ public final class InterIndexDiffFilter extends TreeFilter {
return false;
}
- /** {@inheritDoc} */
@Override
public boolean shouldBeRecursive() {
return false;
}
- /** {@inheritDoc} */
@Override
public TreeFilter clone() {
return this;
}
- /** {@inheritDoc} */
@Override
public String toString() {
return "INTERINDEX_DIFF"; //$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotIgnoredFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotIgnoredFilter.java
index 7d04f27f3c..25947da08f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotIgnoredFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotIgnoredFilter.java
@@ -33,7 +33,6 @@ public class NotIgnoredFilter extends TreeFilter {
this.index = workdirTreeIndex;
}
- /** {@inheritDoc} */
@Override
public boolean include(TreeWalk tw) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
@@ -41,20 +40,17 @@ public class NotIgnoredFilter extends TreeFilter {
return i == null || !i.isEntryIgnored();
}
- /** {@inheritDoc} */
@Override
public boolean shouldBeRecursive() {
return false;
}
- /** {@inheritDoc} */
@Override
public TreeFilter clone() {
// immutable
return this;
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String 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
index 4fb615321f..e9cd83c2e2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java
@@ -38,13 +38,11 @@ public class NotTreeFilter extends TreeFilter {
a = one;
}
- /** {@inheritDoc} */
@Override
public TreeFilter negate() {
return a;
}
- /** {@inheritDoc} */
@Override
public boolean include(TreeWalk walker)
throws MissingObjectException, IncorrectObjectTypeException,
@@ -52,7 +50,6 @@ public class NotTreeFilter extends TreeFilter {
return matchFilter(walker) == 0;
}
- /** {@inheritDoc} */
@Override
public int matchFilter(TreeWalk walker)
throws MissingObjectException, IncorrectObjectTypeException,
@@ -69,20 +66,17 @@ public class NotTreeFilter extends TreeFilter {
return -1;
}
- /** {@inheritDoc} */
@Override
public boolean shouldBeRecursive() {
return a.shouldBeRecursive();
}
- /** {@inheritDoc} */
@Override
public TreeFilter clone() {
final TreeFilter n = a.clone();
return n == a ? this : new NotTreeFilter(n);
}
- /** {@inheritDoc} */
@Override
public String toString() {
return "NOT " + a.toString(); //$NON-NLS-1$
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
index 3c18a9f98d..ce2382552b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java
@@ -12,11 +12,14 @@
package org.eclipse.jgit.treewalk.filter;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Collection;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
/**
@@ -116,6 +119,13 @@ public abstract class OrTreeFilter extends TreeFilter {
}
@Override
+ public boolean shouldTreeWalk(RevCommit c, RevWalk rw,
+ MutableBoolean cpfUsed) {
+ return a.shouldTreeWalk(c, rw, cpfUsed)
+ || b.shouldTreeWalk(c, rw, cpfUsed);
+ }
+
+ @Override
public boolean shouldBeRecursive() {
return a.shouldBeRecursive() || b.shouldBeRecursive();
}
@@ -164,6 +174,13 @@ public abstract class OrTreeFilter extends TreeFilter {
}
@Override
+ public boolean shouldTreeWalk(RevCommit c, RevWalk rw,
+ MutableBoolean cpfUsed) {
+ return Arrays.stream(subfilters)
+ .anyMatch(t -> t.shouldTreeWalk(c, rw, cpfUsed));
+ }
+
+ @Override
public boolean shouldBeRecursive() {
for (TreeFilter f : subfilters)
if (f.shouldBeRecursive())
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
index c94215fcb1..62422817ac 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java
@@ -11,6 +11,10 @@
package org.eclipse.jgit.treewalk.filter;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
+
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.treewalk.TreeWalk;
@@ -45,7 +49,8 @@ public class PathFilter extends TreeFilter {
while (path.endsWith("/")) //$NON-NLS-1$
path = path.substring(0, path.length() - 1);
if (path.length() == 0)
- throw new IllegalArgumentException(JGitText.get().emptyPathNotPermitted);
+ throw new IllegalArgumentException(
+ JGitText.get().emptyPathNotPermitted);
return new PathFilter(path);
}
@@ -67,19 +72,16 @@ public class PathFilter extends TreeFilter {
return pathStr;
}
- /** {@inheritDoc} */
@Override
public boolean include(TreeWalk walker) {
return matchFilter(walker) <= 0;
}
- /** {@inheritDoc} */
@Override
public int matchFilter(TreeWalk walker) {
return walker.isPathMatch(pathRaw, pathRaw.length);
}
- /** {@inheritDoc} */
@Override
public boolean shouldBeRecursive() {
for (byte b : pathRaw)
@@ -88,13 +90,18 @@ public class PathFilter extends TreeFilter {
return false;
}
+ @Override
+ public Optional<Set<byte[]>> getPathsBestEffort() {
+ Set<byte[]> s = Collections.singleton(pathRaw);
+ return Optional.of(s);
+ }
+
/** {@inheritDoc} */
@Override
public PathFilter clone() {
return this;
}
- /** {@inheritDoc} */
@Override
@SuppressWarnings("nls")
public String toString() {
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
index 59855572f2..4c0604ad56 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java
@@ -12,6 +12,8 @@
package org.eclipse.jgit.treewalk.filter;
import java.util.Collection;
+import java.util.Optional;
+import java.util.Set;
import org.eclipse.jgit.errors.StopWalkException;
import org.eclipse.jgit.internal.JGitText;
@@ -232,6 +234,15 @@ public class PathFilterGroup {
}
@Override
+ public Optional<Set<byte[]>> getPathsBestEffort() {
+ Set<byte[]> result = fullpaths.toSet();
+ if (result.isEmpty()) {
+ return Optional.empty();
+ }
+ return Optional.of(result);
+ }
+
+ @Override
public TreeFilter clone() {
return this;
}
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
index 3816d5ed00..ec25903211 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathSuffixFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathSuffixFilter.java
@@ -56,13 +56,11 @@ public class PathSuffixFilter extends TreeFilter {
pathRaw = Constants.encode(pathStr);
}
- /** {@inheritDoc} */
@Override
public TreeFilter clone() {
return this;
}
- /** {@inheritDoc} */
@Override
public boolean include(TreeWalk walker) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
@@ -82,7 +80,6 @@ public class PathSuffixFilter extends TreeFilter {
return super.matchFilter(walker);
}
- /** {@inheritDoc} */
@Override
public boolean shouldBeRecursive() {
return true;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/SkipWorkTreeFilter.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/SkipWorkTreeFilter.java
index 1ed2ef32cd..e311523033 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/SkipWorkTreeFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/SkipWorkTreeFilter.java
@@ -36,7 +36,6 @@ public class SkipWorkTreeFilter extends TreeFilter {
this.treeIdx = treeIdx;
}
- /** {@inheritDoc} */
@Override
public boolean include(TreeWalk walker) {
DirCacheIterator i = walker.getTree(treeIdx, DirCacheIterator.class);
@@ -47,19 +46,16 @@ public class SkipWorkTreeFilter extends TreeFilter {
return e == null || !e.isSkipWorkTree();
}
- /** {@inheritDoc} */
@Override
public boolean shouldBeRecursive() {
return false;
}
- /** {@inheritDoc} */
@Override
public TreeFilter clone() {
return this;
}
- /** {@inheritDoc} */
@SuppressWarnings("nls")
@Override
public String toString() {
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
index 6dbd508e48..8159843312 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java
@@ -11,10 +11,15 @@
package org.eclipse.jgit.treewalk.filter;
import java.io.IOException;
+import java.util.Optional;
+import java.util.Set;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.dircache.DirCacheIterator;
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.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
@@ -188,10 +193,8 @@ public abstract class TreeFilter {
* as thrown by {@link #include(TreeWalk)}
* @since 4.7
*/
- public int matchFilter(TreeWalk walker)
- throws MissingObjectException, IncorrectObjectTypeException,
- IOException
- {
+ public int matchFilter(TreeWalk walker) throws MissingObjectException,
+ IncorrectObjectTypeException, IOException {
return include(walker) ? 0 : 1;
}
@@ -210,6 +213,43 @@ public abstract class TreeFilter {
public abstract boolean shouldBeRecursive();
/**
+ * Return true if the tree entries within this commit require
+ * {@link #include(TreeWalk)} to correctly determine whether they are
+ * interesting to report.
+ * <p>
+ * Otherwise, all tree entries within this commit are UNINTERESTING for this
+ * tree filter.
+ *
+ * @param c
+ * the commit being considered by the TreeFilter.
+ * @param rw
+ * the RevWalk used in retrieving relevant commit data.
+ * @param cpfUsed
+ * if not null, it reports if the changedPathFilter was used in
+ * this method
+ * @return True if the tree entries within c require
+ * {@link #include(TreeWalk)}.
+ * @since 7.3
+ */
+ public boolean shouldTreeWalk(RevCommit c, RevWalk rw,
+ @Nullable MutableBoolean cpfUsed) {
+ return true;
+ }
+
+ /**
+ * If this filter checks that a specific set of paths have all been
+ * modified, returns that set of paths to be checked against a changed path
+ * filter. Otherwise, returns empty.
+ *
+ * @return a set of paths, or empty
+ * @deprecated use {@code shouldTreeWalk} instead.
+ */
+ @Deprecated(since = "7.3")
+ public Optional<Set<byte[]>> getPathsBestEffort() {
+ return Optional.empty();
+ }
+
+ /**
* {@inheritDoc}
*
* Clone this tree filter, including its parameters.
@@ -220,7 +260,6 @@ public abstract class TreeFilter {
@Override
public abstract TreeFilter clone();
- /** {@inheritDoc} */
@Override
public String toString() {
String n = getClass().getName();
@@ -230,4 +269,33 @@ public abstract class TreeFilter {
}
return n.replace('$', '.');
}
+
+ /**
+ * Mutable wrapper to return a boolean in a function parameter.
+ *
+ * @since 7.3
+ */
+ public static class MutableBoolean {
+ private boolean value;
+
+ /**
+ * Return the boolean value.
+ *
+ * @return The state of the internal boolean value.
+ */
+ public boolean get() {
+ return value;
+ }
+
+ void orValue(boolean v) {
+ value = value || v;
+ }
+
+ /**
+ * Reset the boolean value.
+ */
+ public void reset() {
+ value = false;
+ }
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java
index 119c96e02e..48d1c50c7d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java
@@ -96,6 +96,7 @@ public class Base64 {
* @param destOffset
* the index where output will be put
*/
+ @SuppressWarnings("UnnecessaryParentheses")
private static void encode3to4(byte[] source, int srcOffset,
int numSigBytes, byte[] destination, int destOffset) {
// We have to shift left 24 in order to flush out the 1's that appear
@@ -201,6 +202,7 @@ public class Base64 {
* the index where output will be put
* @return the number of decoded bytes converted
*/
+ @SuppressWarnings("UnnecessaryParentheses")
private static int decode4to3(byte[] source, int srcOffset,
byte[] destination, int destOffset) {
// Example: Dk==
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java
index 527c5a69df..557e2cde3b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/BlockList.java
@@ -75,13 +75,11 @@ public class BlockList<T> extends AbstractList<T> {
tailBlock = directory[0];
}
- /** {@inheritDoc} */
@Override
public int size() {
return size;
}
- /** {@inheritDoc} */
@Override
public void clear() {
for (T[] block : directory) {
@@ -94,7 +92,6 @@ public class BlockList<T> extends AbstractList<T> {
tailBlock = directory[0];
}
- /** {@inheritDoc} */
@Override
public T get(int index) {
if (index < 0 || size <= index)
@@ -102,7 +99,6 @@ public class BlockList<T> extends AbstractList<T> {
return directory[toDirectoryIndex(index)][toBlockIndex(index)];
}
- /** {@inheritDoc} */
@Override
public T set(int index, T element) {
if (index < 0 || size <= index)
@@ -160,7 +156,6 @@ public class BlockList<T> extends AbstractList<T> {
}
}
- /** {@inheritDoc} */
@Override
public boolean add(T element) {
int i = tailBlkIdx;
@@ -191,7 +186,6 @@ public class BlockList<T> extends AbstractList<T> {
return true;
}
- /** {@inheritDoc} */
@Override
public void add(int index, T element) {
if (index == size) {
@@ -213,7 +207,6 @@ public class BlockList<T> extends AbstractList<T> {
}
}
- /** {@inheritDoc} */
@Override
public T remove(int index) {
if (index == size - 1) {
@@ -253,7 +246,6 @@ public class BlockList<T> extends AbstractList<T> {
tailBlock = directory[tailDirIdx];
}
- /** {@inheritDoc} */
@Override
public Iterator<T> iterator() {
return new MyIterator();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java
index 5815c62e89..d8183eb8be 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/CachedAuthenticator.java
@@ -32,7 +32,6 @@ public abstract class CachedAuthenticator extends Authenticator {
cached.add(ca);
}
- /** {@inheritDoc} */
@Override
protected final PasswordAuthentication getPasswordAuthentication() {
final String host = getRequestingHost();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
index 12af374b2e..c8421d6012 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
@@ -86,8 +86,8 @@ public class ChangeIdUtil {
}
}
- private static final Pattern issuePattern = Pattern
- .compile("^(Bug|Issue)[a-zA-Z0-9-]*:.*$"); //$NON-NLS-1$
+ private static final Pattern signedOffByPattern = Pattern
+ .compile("^Signed-off-by:.*$"); //$NON-NLS-1$
private static final Pattern footerPattern = Pattern
.compile("(^[a-zA-Z0-9-]+:(?!//).*$)"); //$NON-NLS-1$
@@ -159,7 +159,7 @@ public class ChangeIdUtil {
int footerFirstLine = indexOfFirstFooterLine(lines);
int insertAfter = footerFirstLine;
for (int i = footerFirstLine; i < lines.length; ++i) {
- if (issuePattern.matcher(lines[i]).matches()) {
+ if (!signedOffByPattern.matcher(lines[i]).matches()) {
insertAfter = i + 1;
continue;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Equality.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Equality.java
index da1684630b..ff136f7b3b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/Equality.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Equality.java
@@ -13,22 +13,24 @@ package org.eclipse.jgit.util;
/**
* Equality utilities.
*
- * @since: 6.2
+ * @since 6.2
*/
public class Equality {
/**
- * Compare by reference
- *
- * @param a
- * First object to compare
- * @param b
- * Second object to compare
- * @return {@code true} if the objects are identical, {@code false}
- * otherwise
- *
- * @since 6.2
- */
+ * Compare by reference
+ *
+ * @param <T>
+ * type of the objects to compare
+ * @param a
+ * First object to compare
+ * @param b
+ * Second object to compare
+ * @return {@code true} if the objects are identical, {@code false}
+ * otherwise
+ *
+ * @since 6.2
+ */
@SuppressWarnings("ReferenceEquality")
public static <T> boolean isSameInstance(T a, T b) {
return a == b;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
index aef9e64e02..6a40fad1db 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -30,9 +30,6 @@ import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
-import java.security.AccessControlException;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
import java.text.MessageFormat;
import java.time.Duration;
import java.time.Instant;
@@ -69,6 +66,7 @@ import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.file.FileSnapshot;
+import org.eclipse.jgit.internal.util.ShutdownHook;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
@@ -98,6 +96,9 @@ public abstract class FS {
private static final Pattern VERSION = Pattern
.compile("\\s(\\d+)\\.(\\d+)\\.(\\d+)"); //$NON-NLS-1$
+ private static final Pattern EMPTY_PATH = Pattern
+ .compile("^[\\p{javaWhitespace}" + File.pathSeparator + "]*$"); //$NON-NLS-1$ //$NON-NLS-2$
+
private volatile Boolean supportSymlinks;
/**
@@ -118,6 +119,7 @@ public abstract class FS {
* Detect the file system
*
* @param cygwinUsed
+ * whether cygwin is used
* @return FS instance
*/
public FS detect(Boolean cygwinUsed) {
@@ -149,8 +151,11 @@ public abstract class FS {
/**
* @param stdout
+ * stdout stream
* @param stderr
+ * stderr stream
* @param rc
+ * return code
*/
public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr,
int rc) {
@@ -160,6 +165,8 @@ public abstract class FS {
}
/**
+ * Get buffered standard output stream
+ *
* @return buffered standard output stream
*/
public TemporaryBuffer getStdout() {
@@ -167,6 +174,8 @@ public abstract class FS {
}
/**
+ * Get buffered standard error stream
+ *
* @return buffered standard error stream
*/
public TemporaryBuffer getStderr() {
@@ -174,6 +183,8 @@ public abstract class FS {
}
/**
+ * Get the return code of the process
+ *
* @return the return code of the process
*/
public int getRc() {
@@ -202,7 +213,7 @@ public abstract class FS {
* </p>
*/
public static final Duration FALLBACK_TIMESTAMP_RESOLUTION = Duration
- .ofMillis(2000);
+ .ofSeconds(2);
/**
* Fallback FileStore attributes used when we can't measure the
@@ -250,31 +261,6 @@ public abstract class FS {
private static final AtomicInteger threadNumber = new AtomicInteger(1);
/**
- * Don't use the default thread factory of the ForkJoinPool for the
- * CompletableFuture; it runs without any privileges, which causes
- * trouble if a SecurityManager is present.
- * <p>
- * Instead use normal daemon threads. They'll belong to the
- * SecurityManager's thread group, or use the one of the calling thread,
- * as appropriate.
- * </p>
- *
- * @see java.util.concurrent.Executors#newCachedThreadPool()
- */
- private static final ExecutorService FUTURE_RUNNER = new ThreadPoolExecutor(
- 0, 5, 30L, TimeUnit.SECONDS,
- new LinkedBlockingQueue<Runnable>(),
- runnable -> {
- Thread t = new Thread(runnable,
- "JGit-FileStoreAttributeReader-" //$NON-NLS-1$
- + threadNumber.getAndIncrement());
- // Make sure these threads don't prevent application/JVM
- // shutdown.
- t.setDaemon(true);
- return t;
- });
-
- /**
* Use a separate executor with at most one thread to synchronize
* writing to the config. We write asynchronously since the config
* itself might be on a different file system, which might otherwise
@@ -287,7 +273,7 @@ public abstract class FS {
*/
private static final ExecutorService SAVE_RUNNER = new ThreadPoolExecutor(
0, 1, 1L, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue<Runnable>(),
+ new LinkedBlockingQueue<>(),
runnable -> {
Thread t = new Thread(runnable,
"JGit-FileStoreAttributeWriter-" //$NON-NLS-1$
@@ -299,18 +285,16 @@ public abstract class FS {
static {
// Shut down the SAVE_RUNNER on System.exit()
+ ShutdownHook.INSTANCE
+ .register(FileStoreAttributes::shutdownSafeRunner);
+ }
+
+ private static void shutdownSafeRunner() {
try {
- Runtime.getRuntime().addShutdownHook(new Thread(() -> {
- try {
- SAVE_RUNNER.shutdownNow();
- SAVE_RUNNER.awaitTermination(100,
- TimeUnit.MILLISECONDS);
- } catch (Exception e) {
- // Ignore; we're shutting down
- }
- }));
- } catch (IllegalStateException e) {
- // ignore - may fail if shutdown is already in progress
+ SAVE_RUNNER.shutdownNow();
+ SAVE_RUNNER.awaitTermination(100, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ // Ignore; we're shutting down
}
}
@@ -379,6 +363,7 @@ public abstract class FS {
private static FileStoreAttributes getFileStoreAttributes(Path dir) {
FileStore s;
+ CompletableFuture<Optional<FileStoreAttributes>> f = null;
try {
if (Files.exists(dir)) {
s = Files.getFileStore(dir);
@@ -401,7 +386,7 @@ public abstract class FS {
return FALLBACK_FILESTORE_ATTRIBUTES;
}
- CompletableFuture<Optional<FileStoreAttributes>> f = CompletableFuture
+ f = CompletableFuture
.supplyAsync(() -> {
Lock lock = locks.computeIfAbsent(s,
l -> new ReentrantLock());
@@ -453,7 +438,7 @@ public abstract class FS {
locks.remove(s);
}
return attributes;
- }, FUTURE_RUNNER);
+ });
f = f.exceptionally(e -> {
LOG.error(e.getLocalizedMessage(), e);
return Optional.empty();
@@ -471,10 +456,13 @@ public abstract class FS {
}
// fall through and return fallback
} catch (IOException | ExecutionException | CancellationException e) {
+ cancel(f);
LOG.error(e.getMessage(), e);
} catch (TimeoutException | SecurityException e) {
+ cancel(f);
// use fallback
} catch (InterruptedException e) {
+ cancel(f);
LOG.error(e.getMessage(), e);
Thread.currentThread().interrupt();
}
@@ -483,6 +471,13 @@ public abstract class FS {
return FALLBACK_FILESTORE_ATTRIBUTES;
}
+ private static void cancel(
+ CompletableFuture<Optional<FileStoreAttributes>> f) {
+ if (f != null) {
+ f.cancel(true);
+ }
+ }
+
@SuppressWarnings("boxing")
private static Duration measureMinimalRacyInterval(Path dir) {
LOG.debug("{}: start measure minimal racy interval in {}", //$NON-NLS-1$
@@ -828,6 +823,8 @@ public abstract class FS {
private Duration minimalRacyInterval;
/**
+ * Get the minimal racy interval
+ *
* @return the measured minimal interval after a file has been modified
* in which we cannot rely on lastModified to detect
* modifications
@@ -837,6 +834,8 @@ public abstract class FS {
}
/**
+ * Get the measured filesystem timestamp resolution
+ *
* @return the measured filesystem timestamp resolution
*/
@NonNull
@@ -849,6 +848,7 @@ public abstract class FS {
* timestamp resolution
*
* @param fsTimestampResolution
+ * resolution of filesystem timestamps
*/
public FileStoreAttributes(
@NonNull Duration fsTimestampResolution) {
@@ -883,21 +883,6 @@ public abstract class FS {
}
/**
- * Whether FileStore attributes should be determined asynchronously
- *
- * @param asynch
- * whether FileStore attributes should be determined
- * asynchronously. If false access to cached attributes may block
- * for some seconds for the first call per FileStore
- * @since 5.1.9
- * @deprecated Use {@link FileStoreAttributes#setBackground} instead
- */
- @Deprecated
- public static void setAsyncFileStoreAttributes(boolean asynch) {
- FileStoreAttributes.setBackground(asynch);
- }
-
- /**
* Auto-detect the appropriate file system abstraction, taking into account
* the presence of a Cygwin installation on the system. Using jgit in
* combination with Cygwin requires a more elaborate (and possibly slower)
@@ -1009,7 +994,7 @@ public abstract class FS {
File tempFile = null;
try {
tempFile = File.createTempFile("tempsymlinktarget", ""); //$NON-NLS-1$ //$NON-NLS-2$
- File linkName = new File(tempFile.getParentFile(), "tempsymlink"); //$NON-NLS-1$
+ File linkName = new File(tempFile.getPath() + "-tempsymlink"); //$NON-NLS-1$
createSymLink(linkName, tempFile.getPath());
supportSymlinks = Boolean.TRUE;
linkName.delete();
@@ -1070,23 +1055,6 @@ public abstract class FS {
* symbolic links, the modification time of the link is returned, rather
* than that of the link target.
*
- * @param f
- * a {@link java.io.File} object.
- * @return last modified time of f
- * @throws java.io.IOException
- * @since 3.0
- * @deprecated use {@link #lastModifiedInstant(Path)} instead
- */
- @Deprecated
- public long lastModified(File f) throws IOException {
- return FileUtils.lastModified(f);
- }
-
- /**
- * Get the last modified time of a file system object. If the OS/JRE support
- * symbolic links, the modification time of the link is returned, rather
- * than that of the link target.
- *
* @param p
* a {@link Path} object.
* @return last modified time of p
@@ -1115,29 +1083,12 @@ public abstract class FS {
* <p>
* For symlinks it sets the modified time of the link target.
*
- * @param f
- * a {@link java.io.File} object.
- * @param time
- * last modified time
- * @throws java.io.IOException
- * @since 3.0
- * @deprecated use {@link #setLastModified(Path, Instant)} instead
- */
- @Deprecated
- public void setLastModified(File f, long time) throws IOException {
- FileUtils.setLastModified(f, time);
- }
-
- /**
- * Set the last modified time of a file system object.
- * <p>
- * For symlinks it sets the modified time of the link target.
- *
* @param p
* a {@link Path} object.
* @param time
* last modified time
* @throws java.io.IOException
+ * if an IO error occurred
* @since 5.1.9
*/
public void setLastModified(Path p, Instant time) throws IOException {
@@ -1152,6 +1103,7 @@ public abstract class FS {
* a {@link java.io.File} object.
* @return length of a file
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.0
*/
public long length(File path) throws IOException {
@@ -1164,7 +1116,7 @@ public abstract class FS {
* @param f
* a {@link java.io.File} object.
* @throws java.io.IOException
- * this may be a Java7 subclass with detailed information
+ * if an IO error occurred
* @since 3.3
*/
public void delete(File f) throws IOException {
@@ -1264,8 +1216,10 @@ public abstract class FS {
* Return all the attributes of a file, without following symbolic links.
*
* @param file
+ * the file
* @return {@link BasicFileAttributes} of the file
- * @throws IOException in case of any I/O errors accessing the file
+ * @throws IOException
+ * in case of any I/O errors accessing the file
*
* @since 4.5.6
*/
@@ -1283,11 +1237,10 @@ public abstract class FS {
}
private File defaultUserHomeImpl() {
- String home = AccessController.doPrivileged(
- (PrivilegedAction<String>) () -> System.getProperty("user.home") //$NON-NLS-1$
- );
- if (home == null || home.length() == 0)
+ String home = SystemReader.getInstance().getProperty("user.home"); //$NON-NLS-1$
+ if (StringUtils.isEmptyOrNull(home)) {
return null;
+ }
return new File(home).getAbsoluteFile();
}
@@ -1303,8 +1256,10 @@ public abstract class FS {
* @return the first match found, or null
* @since 3.0
*/
+ @SuppressWarnings("StringSplitter")
protected static File searchPath(String path, String... lookFor) {
- if (path == null) {
+ if (StringUtils.isEmptyOrNull(path)
+ || EMPTY_PATH.matcher(path).find()) {
return null;
}
@@ -1421,13 +1376,6 @@ public abstract class FS {
}
} catch (IOException e) {
LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$
- } catch (AccessControlException e) {
- LOG.warn(MessageFormat.format(
- JGitText.get().readPipeIsNotAllowedRequiredPermission,
- command, dir, e.getPermission()));
- } catch (SecurityException e) {
- LOG.warn(MessageFormat.format(JGitText.get().readPipeIsNotAllowed,
- command, dir));
}
if (debug) {
LOG.debug("readpipe returns null"); //$NON-NLS-1$
@@ -1671,6 +1619,7 @@ public abstract class FS {
* a {@link java.io.File} object.
* @return target of link or null
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.0
*/
public String readSymLink(File path) throws IOException {
@@ -1684,6 +1633,7 @@ public abstract class FS {
* a {@link java.io.File} object.
* @return true if the path is a symbolic link (and we support these)
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.0
*/
public boolean isSymLink(File path) throws IOException {
@@ -1738,6 +1688,7 @@ public abstract class FS {
* @return true if path is hidden, either starts with . on unix or has the
* hidden attribute in windows
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.0
*/
public boolean isHidden(File path) throws IOException {
@@ -1752,6 +1703,7 @@ public abstract class FS {
* @param hidden
* whether to set the file hidden
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.0
*/
public void setHidden(File path, boolean hidden) throws IOException {
@@ -1766,6 +1718,7 @@ public abstract class FS {
* @param target
* target path of the symlink
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.0
*/
public void createSymLink(File path, String target) throws IOException {
@@ -1773,24 +1726,6 @@ public abstract class FS {
}
/**
- * Create a new file. See {@link java.io.File#createNewFile()}. Subclasses
- * of this class may take care to provide a safe implementation for this
- * even if {@link #supportsAtomicCreateNewFile()} is <code>false</code>
- *
- * @param path
- * the file to be created
- * @return <code>true</code> if the file was created, <code>false</code> if
- * the file already existed
- * @throws java.io.IOException
- * @deprecated use {@link #createNewFileAtomic(File)} instead
- * @since 4.5
- */
- @Deprecated
- public boolean createNewFile(File path) throws IOException {
- return path.createNewFile();
- }
-
- /**
* A token representing a file created by
* {@link #createNewFileAtomic(File)}. The token must be retained until the
* file has been deleted in order to guarantee that the unique file was
@@ -1810,6 +1745,8 @@ public abstract class FS {
}
/**
+ * Whether the file was created successfully
+ *
* @return {@code true} if the file was created successfully
*/
public boolean isCreated() {
@@ -1852,6 +1789,7 @@ public abstract class FS {
* @return LockToken this token must be closed after the created file was
* deleted
* @throws IOException
+ * if an IO error occurred
* @since 4.7
*/
public LockToken createNewFileAtomic(File path) throws IOException {
@@ -2011,6 +1949,8 @@ public abstract class FS {
environment.put(Constants.GIT_DIR_KEY,
repository.getDirectory().getAbsolutePath());
if (!repository.isBare()) {
+ environment.put(Constants.GIT_COMMON_DIR_KEY,
+ repository.getCommonDirectory().getAbsolutePath());
environment.put(Constants.GIT_WORK_TREE_KEY,
repository.getWorkTree().getAbsolutePath());
}
@@ -2106,7 +2046,7 @@ public abstract class FS {
case "post-receive": //$NON-NLS-1$
case "post-update": //$NON-NLS-1$
case "push-to-checkout": //$NON-NLS-1$
- return repository.getDirectory();
+ return repository.getCommonDirectory();
default:
return repository.getWorkTree();
}
@@ -2119,7 +2059,7 @@ public abstract class FS {
if (hooksDir != null) {
return new File(hooksDir);
}
- File dir = repository.getDirectory();
+ File dir = repository.getCommonDirectory();
return dir == null ? null : new File(dir, Constants.HOOKS);
}
@@ -2317,7 +2257,9 @@ public abstract class FS {
* The standard input stream passed to the process
* @return The result of the executed command
* @throws java.lang.InterruptedException
+ * if thread was interrupted
* @throws java.io.IOException
+ * if an IO error occurred
* @since 4.2
*/
public ExecutionResult execute(ProcessBuilder pb, InputStream in)
@@ -2346,6 +2288,8 @@ public abstract class FS {
public static class Attributes {
/**
+ * Whether this are attributes of a directory
+ *
* @return true if this are the attributes of a directory
*/
public boolean isDirectory() {
@@ -2353,6 +2297,8 @@ public abstract class FS {
}
/**
+ * Whether this are attributes of an executable file
+ *
* @return true if this are the attributes of an executable file
*/
public boolean isExecutable() {
@@ -2360,6 +2306,8 @@ public abstract class FS {
}
/**
+ * Whether this are the attributes of a symbolic link
+ *
* @return true if this are the attributes of a symbolic link
*/
public boolean isSymbolicLink() {
@@ -2367,6 +2315,8 @@ public abstract class FS {
}
/**
+ * Whether this are the attributes of a regular file
+ *
* @return true if this are the attributes of a regular file
*/
public boolean isRegularFile() {
@@ -2374,6 +2324,8 @@ public abstract class FS {
}
/**
+ * Get the file creation time
+ *
* @return the time when the file was created
*/
public long getCreationTime() {
@@ -2381,16 +2333,8 @@ public abstract class FS {
}
/**
- * @return the time (milliseconds since 1970-01-01) when this object was
- * last modified
- * @deprecated use getLastModifiedInstant instead
- */
- @Deprecated
- public long getLastModifiedTime() {
- return lastModifiedInstant.toEpochMilli();
- }
-
- /**
+ * Get the time when this object was last modified
+ *
* @return the time when this object was last modified
* @since 5.1.9
*/
@@ -2441,14 +2385,18 @@ public abstract class FS {
* Constructor when there are issues with reading. All attributes except
* given will be set to the default values.
*
- * @param fs
* @param path
+ * file path
+ * @param fs
+ * FS to use
*/
public Attributes(File path, FS fs) {
this(fs, path, false, false, false, false, false, 0L, EPOCH, 0L);
}
/**
+ * Get the length of this file
+ *
* @return length of this file object
*/
public long getLength() {
@@ -2458,6 +2406,8 @@ public abstract class FS {
}
/**
+ * Get the filename
+ *
* @return the filename
*/
public String getName() {
@@ -2465,6 +2415,8 @@ public abstract class FS {
}
/**
+ * Get the file the attributes apply to
+ *
* @return the file the attributes apply to
*/
public File getFile() {
@@ -2522,6 +2474,33 @@ public abstract class FS {
}
/**
+ * Get common dir path.
+ *
+ * @param dir
+ * the .git folder
+ * @return common dir path
+ * @throws IOException
+ * if commondir file can't be read
+ *
+ * @since 7.0
+ */
+ public File getCommonDir(File dir) throws IOException {
+ // first the GIT_COMMON_DIR is same as GIT_DIR
+ File commonDir = dir;
+ // now check if commondir file exists (e.g. worktree repository)
+ File commonDirFile = new File(dir, Constants.COMMONDIR_FILE);
+ if (commonDirFile.isFile()) {
+ String commonDirPath = new String(IO.readFully(commonDirFile))
+ .trim();
+ commonDir = new File(commonDirPath);
+ if (!commonDir.isAbsolute()) {
+ commonDir = new File(dir, commonDirPath).getCanonicalFile();
+ }
+ }
+ return commonDir;
+ }
+
+ /**
* This runnable will consume an input stream's content into an output
* stream as soon as it gets available.
* <p>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
index 1c113617f8..db2b5b4f71 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010, Robin Rosenberg and others
+ * Copyright (C) 2010, 2024, Robin Rosenberg and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -203,7 +203,16 @@ public class FS_POSIX extends FS {
/** {@inheritDoc} */
@Override
public boolean canExecute(File f) {
- return FileUtils.canExecute(f);
+ if (!isFile(f)) {
+ return false;
+ }
+ try {
+ Path path = FileUtils.toPath(f);
+ Set<PosixFilePermission> pset = Files.getPosixFilePermissions(path);
+ return pset.contains(PosixFilePermission.OWNER_EXECUTE);
+ } catch (IOException ex) {
+ return false;
+ }
}
/** {@inheritDoc} */
@@ -250,8 +259,12 @@ public class FS_POSIX extends FS {
/** {@inheritDoc} */
@Override
public ProcessBuilder runInShell(String cmd, String[] args) {
- List<String> argv = new ArrayList<>(4 + args.length);
+ List<String> argv = new ArrayList<>(5 + args.length);
argv.add("sh"); //$NON-NLS-1$
+ if (SystemReader.getInstance().isMacOS()) {
+ // Use a login shell to get the full normal $PATH
+ argv.add("-l"); //$NON-NLS-1$
+ }
argv.add("-c"); //$NON-NLS-1$
argv.add(cmd + " \"$@\""); //$NON-NLS-1$
argv.add(cmd);
@@ -328,73 +341,6 @@ public class FS_POSIX extends FS {
return supportsAtomicFileCreation == AtomicFileCreation.SUPPORTED;
}
- @Override
- @SuppressWarnings("boxing")
- /**
- * {@inheritDoc}
- * <p>
- * An implementation of the File#createNewFile() semantics which works also
- * on NFS. If the config option
- * {@code core.supportsAtomicCreateNewFile = true} (which is the default)
- * then simply File#createNewFile() is called.
- *
- * But if {@code core.supportsAtomicCreateNewFile = false} then after
- * successful creation of the lock file a hard link to that lock file is
- * created and the attribute nlink of the lock file is checked to be 2. If
- * multiple clients manage to create the same lock file nlink would be
- * greater than 2 showing the error.
- *
- * @see "https://www.time-travellers.org/shane/papers/NFS_considered_harmful.html"
- *
- * @deprecated use {@link FS_POSIX#createNewFileAtomic(File)} instead
- * @since 4.5
- */
- @Deprecated
- public boolean createNewFile(File lock) throws IOException {
- if (!lock.createNewFile()) {
- return false;
- }
- if (supportsAtomicCreateNewFile()) {
- return true;
- }
- Path lockPath = lock.toPath();
- Path link = null;
- FileStore store = null;
- try {
- store = Files.getFileStore(lockPath);
- } catch (SecurityException e) {
- return true;
- }
- try {
- Boolean canLink = CAN_HARD_LINK.computeIfAbsent(store,
- s -> Boolean.TRUE);
- if (Boolean.FALSE.equals(canLink)) {
- return true;
- }
- link = Files.createLink(
- Paths.get(lock.getAbsolutePath() + ".lnk"), //$NON-NLS-1$
- lockPath);
- Integer nlink = (Integer) (Files.getAttribute(lockPath,
- "unix:nlink")); //$NON-NLS-1$
- if (nlink > 2) {
- LOG.warn(MessageFormat.format(
- JGitText.get().failedAtomicFileCreation, lockPath,
- nlink));
- return false;
- } else if (nlink < 2) {
- CAN_HARD_LINK.put(store, Boolean.FALSE);
- }
- return true;
- } catch (UnsupportedOperationException | IllegalArgumentException e) {
- CAN_HARD_LINK.put(store, Boolean.FALSE);
- return true;
- } finally {
- if (link != null) {
- Files.delete(link);
- }
- }
- }
-
/**
* {@inheritDoc}
* <p>
@@ -418,6 +364,7 @@ public class FS_POSIX extends FS {
* @return LockToken this lock token must be held until the file is no
* longer needed
* @throws IOException
+ * if an IO error occurred
* @since 5.0
*/
@Override
@@ -446,8 +393,7 @@ public class FS_POSIX extends FS {
return token(true, null);
}
link = Files.createLink(Paths.get(uniqueLinkPath(file)), path);
- Integer nlink = (Integer) (Files.getAttribute(path,
- "unix:nlink")); //$NON-NLS-1$
+ Integer nlink = (Integer) Files.getAttribute(path, "unix:nlink"); //$NON-NLS-1$
if (nlink.intValue() > 2) {
LOG.warn(MessageFormat.format(
JGitText.get().failedAtomicFileCreation, path, nlink));
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
index ae73d3feb8..5926655b7b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java
@@ -58,43 +58,36 @@ public class FS_Win32 extends FS {
super(src);
}
- /** {@inheritDoc} */
@Override
public FS newInstance() {
return new FS_Win32(this);
}
- /** {@inheritDoc} */
@Override
public boolean supportsExecute() {
return false;
}
- /** {@inheritDoc} */
@Override
public boolean canExecute(File f) {
return false;
}
- /** {@inheritDoc} */
@Override
public boolean setExecute(File f, boolean canExec) {
return false;
}
- /** {@inheritDoc} */
@Override
public boolean isCaseSensitive() {
return false;
}
- /** {@inheritDoc} */
@Override
public boolean retryFailedLockFileCommit() {
return true;
}
- /** {@inheritDoc} */
@Override
public Entry[] list(File directory, FileModeStrategy fileModeStrategy) {
if (!Files.isDirectory(directory.toPath(), LinkOption.NOFOLLOW_LINKS)) {
@@ -140,7 +133,6 @@ public class FS_Win32 extends FS {
return result.toArray(new Entry[0]);
}
- /** {@inheritDoc} */
@Override
protected File discoverGitExe() {
String path = SystemReader.getInstance().getenv("PATH"); //$NON-NLS-1$
@@ -171,7 +163,6 @@ public class FS_Win32 extends FS {
return gitExe;
}
- /** {@inheritDoc} */
@Override
protected File userHomeImpl() {
String home = SystemReader.getInstance().getenv("HOME"); //$NON-NLS-1$
@@ -194,7 +185,6 @@ public class FS_Win32 extends FS {
return super.userHomeImpl();
}
- /** {@inheritDoc} */
@Override
public ProcessBuilder runInShell(String cmd, String[] args) {
List<String> argv = new ArrayList<>(3 + args.length);
@@ -207,7 +197,6 @@ public class FS_Win32 extends FS {
return proc;
}
- /** {@inheritDoc} */
@Override
public Attributes getAttributes(File path) {
return FileUtils.getFileAttributesBasic(this, path);
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
index add5498175..237879110a 100644
--- 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
@@ -14,8 +14,6 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.File;
import java.io.OutputStream;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -43,10 +41,7 @@ public class FS_Win32_Cygwin extends FS_Win32 {
* @return true if cygwin is found
*/
public static boolean isCygwin() {
- final String path = AccessController
- .doPrivileged((PrivilegedAction<String>) () -> System
- .getProperty("java.library.path") //$NON-NLS-1$
- );
+ final String path = System.getProperty("java.library.path"); //$NON-NLS-1$
if (path == null)
return false;
File found = FS.searchPath(path, "cygpath.exe"); //$NON-NLS-1$
@@ -72,13 +67,11 @@ public class FS_Win32_Cygwin extends FS_Win32 {
super(src);
}
- /** {@inheritDoc} */
@Override
public FS newInstance() {
return new FS_Win32_Cygwin(this);
}
- /** {@inheritDoc} */
@Override
public File resolve(File dir, String pn) {
String useCygPath = System.getProperty("jgit.usecygpath"); //$NON-NLS-1$
@@ -99,18 +92,14 @@ public class FS_Win32_Cygwin extends FS_Win32 {
return super.resolve(dir, pn);
}
- /** {@inheritDoc} */
@Override
protected File userHomeImpl() {
- final String home = AccessController.doPrivileged(
- (PrivilegedAction<String>) () -> System.getenv("HOME") //$NON-NLS-1$
- );
+ final String home = System.getenv("HOME"); //$NON-NLS-1$
if (home == null || home.length() == 0)
return super.userHomeImpl();
return resolve(new File("."), home); //$NON-NLS-1$
}
- /** {@inheritDoc} */
@Override
public ProcessBuilder runInShell(String cmd, String[] args) {
List<String> argv = new ArrayList<>(4 + args.length);
@@ -129,14 +118,12 @@ public class FS_Win32_Cygwin extends FS_Win32 {
return QuotedString.BOURNE.quote(cmd.replace(File.separatorChar, '/'));
}
- /** {@inheritDoc} */
@Override
public String relativize(String base, String other) {
final String relativized = super.relativize(base, other);
return relativized.replace(File.separatorChar, '/');
}
- /** {@inheritDoc} */
@Override
public ProcessResult runHookIfPresent(Repository repository, String hookName,
String[] args, OutputStream outRedirect, OutputStream errRedirect,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
index f013e7e095..39c67f1b86 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
@@ -288,12 +288,14 @@ public class FileUtils {
* @throws java.nio.file.AtomicMoveNotSupportedException
* if file cannot be moved as an atomic file system operation
* @throws java.io.IOException
+ * if an IO error occurred
* @since 4.1
*/
public static void rename(final File src, final File dst,
CopyOption... options)
throws AtomicMoveNotSupportedException, IOException {
int attempts = FS.DETECTED.retryFailedLockFileCommit() ? 10 : 1;
+ IOException finalError = null;
while (--attempts >= 0) {
try {
Files.move(toPath(src), toPath(dst), options);
@@ -301,29 +303,35 @@ public class FileUtils {
} catch (AtomicMoveNotSupportedException e) {
throw e;
} catch (IOException e) {
- try {
- if (!dst.delete()) {
- delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
+ if (attempts == 0) {
+ // Only delete on the last attempt.
+ try {
+ if (!dst.delete()) {
+ delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
+ }
+ // On *nix there is no try, you do or do not
+ Files.move(toPath(src), toPath(dst), options);
+ return;
+ } catch (IOException e2) {
+ e2.addSuppressed(e);
+ finalError = e2;
}
- // On *nix there is no try, you do or do not
- Files.move(toPath(src), toPath(dst), options);
- return;
- } catch (IOException e2) {
- // ignore and continue retry
}
}
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- throw new IOException(
- MessageFormat.format(JGitText.get().renameFileFailed,
- src.getAbsolutePath(), dst.getAbsolutePath()),
- e);
+ if (attempts > 0) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ throw new IOException(MessageFormat.format(
+ JGitText.get().renameFileFailed,
+ src.getAbsolutePath(), dst.getAbsolutePath()), e);
+ }
}
}
throw new IOException(
MessageFormat.format(JGitText.get().renameFileFailed,
- src.getAbsolutePath(), dst.getAbsolutePath()));
+ src.getAbsolutePath(), dst.getAbsolutePath()),
+ finalError);
}
/**
@@ -446,6 +454,7 @@ public class FileUtils {
* the target of the symbolic link
* @return the path to the symbolic link
* @throws java.io.IOException
+ * if an IO error occurred
* @since 4.2
*/
public static Path createSymLink(File path, String target)
@@ -474,6 +483,7 @@ public class FileUtils {
* a {@link java.io.File} object.
* @return target path of the symlink, or null if it is not a symbolic link
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.0
*/
public static String readSymLink(File path) throws IOException {
@@ -499,6 +509,7 @@ public class FileUtils {
* The parent dir, can be null to use system default temp dir.
* @return the temp dir created.
* @throws java.io.IOException
+ * if an IO error occurred
* @since 3.4
*/
public static File createTempDir(String prefix, String suffix, File dir)
@@ -620,11 +631,11 @@ public class FileUtils {
}
/**
- * Determine if an IOException is a Stale NFS File Handle
+ * Determine if an IOException is a stale NFS file handle
*
* @param ioe
* an {@link java.io.IOException} object.
- * @return a boolean true if the IOException is a Stale NFS FIle Handle
+ * @return a boolean true if the IOException is a stale NFS file handle
* @since 4.1
*/
public static boolean isStaleFileHandle(IOException ioe) {
@@ -635,13 +646,13 @@ public class FileUtils {
}
/**
- * Determine if a throwable or a cause in its causal chain is a Stale NFS
- * File Handle
+ * Determine if a throwable or a cause in its causal chain is a stale NFS
+ * file handle
*
* @param throwable
* a {@link java.lang.Throwable} object.
* @return a boolean true if the throwable or a cause in its causal chain is
- * a Stale NFS File Handle
+ * a stale NFS file handle
* @since 4.7
*/
public static boolean isStaleFileHandleInCausalChain(Throwable throwable) {
@@ -749,7 +760,10 @@ public class FileUtils {
}
/**
+ * Check if file is a symlink
+ *
* @param file
+ * the file to be checked if it is a symbolic link
* @return {@code true} if the passed file is a symbolic link
*/
static boolean isSymlink(File file) {
@@ -757,21 +771,10 @@ public class FileUtils {
}
/**
- * @param file
- * @return lastModified attribute for given file, not following symbolic
- * links
- * @throws IOException
- * @deprecated use {@link #lastModifiedInstant(Path)} instead which returns
- * FileTime
- */
- @Deprecated
- static long lastModified(File file) throws IOException {
- return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS)
- .toMillis();
- }
-
- /**
+ * Get last modified timestamp of a file
+ *
* @param path
+ * file path
* @return lastModified attribute for given file, not following symbolic
* links
*/
@@ -795,8 +798,10 @@ public class FileUtils {
* Return all the attributes of a file, without following symbolic links.
*
* @param file
+ * the file
* @return {@link BasicFileAttributes} of the file
- * @throws IOException in case of any I/O errors accessing the file
+ * @throws IOException
+ * in case of any I/O errors accessing the file
*
* @since 4.5.6
*/
@@ -807,21 +812,12 @@ public class FileUtils {
/**
* Set the last modified time of a file system object.
*
- * @param file
- * @param time
- * @throws IOException
- */
- @Deprecated
- static void setLastModified(File file, long time) throws IOException {
- Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time));
- }
-
- /**
- * Set the last modified time of a file system object.
- *
* @param path
+ * file path
* @param time
+ * last modified timestamp of the file
* @throws IOException
+ * if an IO error occurred
*/
static void setLastModified(Path path, Instant time)
throws IOException {
@@ -829,7 +825,10 @@ public class FileUtils {
}
/**
+ * Whether the file exists
+ *
* @param file
+ * the file
* @return {@code true} if the given file exists, not following symbolic
* links
*/
@@ -838,9 +837,13 @@ public class FileUtils {
}
/**
+ * Check if file is hidden (on Windows)
+ *
* @param file
+ * the file
* @return {@code true} if the given file is hidden
* @throws IOException
+ * if an IO error occurred
*/
static boolean isHidden(File file) throws IOException {
return Files.isHidden(toPath(file));
@@ -854,6 +857,7 @@ public class FileUtils {
* @param hidden
* a boolean.
* @throws java.io.IOException
+ * if an IO error occurred
* @since 4.1
*/
public static void setHidden(File file, boolean hidden) throws IOException {
@@ -868,6 +872,7 @@ public class FileUtils {
* a {@link java.io.File}.
* @return length of the given file
* @throws java.io.IOException
+ * if an IO error occurred
* @since 4.1
*/
public static long getLength(File file) throws IOException {
@@ -879,7 +884,10 @@ public class FileUtils {
}
/**
+ * Check if file is directory
+ *
* @param file
+ * the file
* @return {@code true} if the given file is a directory, not following
* symbolic links
*/
@@ -888,7 +896,10 @@ public class FileUtils {
}
/**
+ * Check if File is a file
+ *
* @param file
+ * the file
* @return {@code true} if the given file is a file, not following symbolic
* links
*/
@@ -929,8 +940,12 @@ public class FileUtils {
}
/**
+ * Get basic file attributes
+ *
* @param fs
+ * a {@link org.eclipse.jgit.util.FS} object.
* @param file
+ * the file
* @return non null attributes object
*/
static Attributes getFileAttributesBasic(FS fs, File file) {
@@ -1079,6 +1094,7 @@ public class FileUtils {
* @param f
* the file to touch
* @throws IOException
+ * if an IO error occurred
* @since 5.1.8
*/
public static void touch(Path f) throws IOException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GSSManagerFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GSSManagerFactory.java
index be56e5ecf5..ba0df932ce 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GSSManagerFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GSSManagerFactory.java
@@ -28,7 +28,7 @@ public abstract class GSSManagerFactory {
* @return detected GSSManager factory
*/
public static GSSManagerFactory detect() {
- return (SunGSSManagerFactory.isSupported()) ? new SunGSSManagerFactory()
+ return SunGSSManagerFactory.isSupported() ? new SunGSSManagerFactory()
: new DefaultGSSManagerFactory();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java
index e6bf497ac4..332e65985e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java
@@ -10,10 +10,10 @@
package org.eclipse.jgit.util;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
import java.util.Locale;
-import java.util.TimeZone;
import org.eclipse.jgit.lib.PersonIdent;
@@ -26,9 +26,9 @@ import org.eclipse.jgit.lib.PersonIdent;
*/
public class GitDateFormatter {
- private DateFormat dateTimeInstance;
+ private DateTimeFormatter dateTimeFormat;
- private DateFormat dateTimeInstance2;
+ private DateTimeFormatter dateTimeFormat2;
private final Format format;
@@ -96,30 +96,34 @@ public class GitDateFormatter {
default:
break;
case DEFAULT: // Not default:
- dateTimeInstance = new SimpleDateFormat(
+ dateTimeFormat = DateTimeFormatter.ofPattern(
"EEE MMM dd HH:mm:ss yyyy Z", Locale.US); //$NON-NLS-1$
break;
case ISO:
- dateTimeInstance = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", //$NON-NLS-1$
+ dateTimeFormat = DateTimeFormatter.ofPattern(
+ "yyyy-MM-dd HH:mm:ss Z", //$NON-NLS-1$
Locale.US);
break;
case LOCAL:
- dateTimeInstance = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy", //$NON-NLS-1$
+ dateTimeFormat = DateTimeFormatter.ofPattern(
+ "EEE MMM dd HH:mm:ss yyyy", //$NON-NLS-1$
Locale.US);
break;
case RFC:
- dateTimeInstance = new SimpleDateFormat(
+ dateTimeFormat = DateTimeFormatter.ofPattern(
"EEE, dd MMM yyyy HH:mm:ss Z", Locale.US); //$NON-NLS-1$
break;
case SHORT:
- dateTimeInstance = new SimpleDateFormat("yyyy-MM-dd", Locale.US); //$NON-NLS-1$
+ dateTimeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd", //$NON-NLS-1$
+ Locale.US);
break;
case LOCALE:
case LOCALELOCAL:
- SystemReader systemReader = SystemReader.getInstance();
- dateTimeInstance = systemReader.getDateTimeInstance(
- DateFormat.DEFAULT, DateFormat.DEFAULT);
- dateTimeInstance2 = systemReader.getSimpleDateFormat("Z"); //$NON-NLS-1$
+ dateTimeFormat = DateTimeFormatter
+ .ofLocalizedDateTime(FormatStyle.MEDIUM)
+ .withLocale(Locale.US);
+ dateTimeFormat2 = DateTimeFormatter.ofPattern("Z", //$NON-NLS-1$
+ Locale.US);
break;
}
}
@@ -135,39 +139,45 @@ public class GitDateFormatter {
@SuppressWarnings("boxing")
public String formatDate(PersonIdent ident) {
switch (format) {
- case RAW:
- int offset = ident.getTimeZoneOffset();
+ case RAW: {
+ int offset = ident.getZoneOffset().getTotalSeconds();
String sign = offset < 0 ? "-" : "+"; //$NON-NLS-1$ //$NON-NLS-2$
int offset2;
- if (offset < 0)
+ if (offset < 0) {
offset2 = -offset;
- else
+ } else {
offset2 = offset;
- int hours = offset2 / 60;
- int minutes = offset2 % 60;
+ }
+ int minutes = (offset2 / 60) % 60;
+ int hours = offset2 / 60 / 60;
return String.format("%d %s%02d%02d", //$NON-NLS-1$
- ident.getWhen().getTime() / 1000, sign, hours, minutes);
+ ident.getWhenAsInstant().getEpochSecond(), sign, hours,
+ minutes);
+ }
case RELATIVE:
- return RelativeDateFormatter.format(ident.getWhen());
+ return RelativeDateFormatter.format(ident.getWhenAsInstant());
case LOCALELOCAL:
case LOCAL:
- dateTimeInstance.setTimeZone(SystemReader.getInstance()
- .getTimeZone());
- return dateTimeInstance.format(ident.getWhen());
- case LOCALE:
- TimeZone tz = ident.getTimeZone();
- if (tz == null)
- tz = SystemReader.getInstance().getTimeZone();
- dateTimeInstance.setTimeZone(tz);
- dateTimeInstance2.setTimeZone(tz);
- return dateTimeInstance.format(ident.getWhen()) + " " //$NON-NLS-1$
- + dateTimeInstance2.format(ident.getWhen());
- default:
- tz = ident.getTimeZone();
- if (tz == null)
- tz = SystemReader.getInstance().getTimeZone();
- dateTimeInstance.setTimeZone(ident.getTimeZone());
- return dateTimeInstance.format(ident.getWhen());
+ return dateTimeFormat
+ .withZone(SystemReader.getInstance().getTimeZoneId())
+ .format(ident.getWhenAsInstant());
+ case LOCALE: {
+ ZoneId tz = ident.getZoneId();
+ if (tz == null) {
+ tz = SystemReader.getInstance().getTimeZoneId();
+ }
+ return dateTimeFormat.withZone(tz).format(ident.getWhenAsInstant())
+ + " " //$NON-NLS-1$
+ + dateTimeFormat2.withZone(tz)
+ .format(ident.getWhenAsInstant());
+ }
+ default: {
+ ZoneId tz = ident.getZoneId();
+ if (tz == null) {
+ tz = SystemReader.getInstance().getTimeZoneId();
+ }
+ return dateTimeFormat.withZone(tz).format(ident.getWhenAsInstant());
+ }
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java
index 6a4b39652a..f080056546 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java
@@ -28,7 +28,10 @@ import org.eclipse.jgit.internal.JGitText;
* used. One example is the parsing of the config parameter gc.pruneexpire. The
* parser can handle only subset of what native gits approxidate parser
* understands.
+ *
+ * @deprecated Use {@link GitTimeParser} instead.
*/
+@Deprecated(since = "7.1")
public class GitDateParser {
/**
* The Date representing never. Though this is a concrete value, most
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java
new file mode 100644
index 0000000000..acaa1ce563
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitTimeParser.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2024 Christian Halstrick and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.util;
+
+import java.text.MessageFormat;
+import java.text.ParseException;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalAccessor;
+import java.util.EnumMap;
+import java.util.Map;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.JGitText;
+
+/**
+ * Parses strings with time and date specifications into
+ * {@link java.time.Instant}.
+ *
+ * When git needs to parse strings specified by the user this parser can be
+ * used. One example is the parsing of the config parameter gc.pruneexpire. The
+ * parser can handle only subset of what native gits approxidate parser
+ * understands.
+ *
+ * @since 7.1
+ */
+public class GitTimeParser {
+
+ private static final Map<ParseableSimpleDateFormat, DateTimeFormatter> formatCache = new EnumMap<>(
+ ParseableSimpleDateFormat.class);
+
+ // An enum of all those formats which this parser can parse with the help of
+ // a DateTimeFormatter. There are other formats (e.g. the relative formats
+ // like "yesterday" or "1 week ago") which this parser can parse but which
+ // are not listed here because they are parsed without the help of a
+ // DateTimeFormatter.
+ enum ParseableSimpleDateFormat {
+ ISO("yyyy-MM-dd HH:mm:ss Z"), // //$NON-NLS-1$
+ RFC("EEE, dd MMM yyyy HH:mm:ss Z"), // //$NON-NLS-1$
+ SHORT("yyyy-MM-dd"), // //$NON-NLS-1$
+ SHORT_WITH_DOTS_REVERSE("dd.MM.yyyy"), // //$NON-NLS-1$
+ SHORT_WITH_DOTS("yyyy.MM.dd"), // //$NON-NLS-1$
+ SHORT_WITH_SLASH("MM/dd/yyyy"), // //$NON-NLS-1$
+ DEFAULT("EEE MMM dd HH:mm:ss yyyy Z"), // //$NON-NLS-1$
+ LOCAL("EEE MMM dd HH:mm:ss yyyy"); //$NON-NLS-1$
+
+ private final String formatStr;
+
+ ParseableSimpleDateFormat(String formatStr) {
+ this.formatStr = formatStr;
+ }
+ }
+
+ private GitTimeParser() {
+ // This class is not supposed to be instantiated
+ }
+
+ /**
+ * Parses a string into a {@link java.time.LocalDateTime} using the default
+ * locale. Since this parser also supports relative formats (e.g.
+ * "yesterday") the caller can specify the reference date. These types of
+ * strings can be parsed:
+ * <ul>
+ * <li>"never"</li>
+ * <li>"now"</li>
+ * <li>"yesterday"</li>
+ * <li>"(x) years|months|weeks|days|hours|minutes|seconds ago"<br>
+ * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of '
+ * ' one can use '.' to separate the words</li>
+ * <li>"yyyy-MM-dd HH:mm:ss Z" (ISO)</li>
+ * <li>"EEE, dd MMM yyyy HH:mm:ss Z" (RFC)</li>
+ * <li>"yyyy-MM-dd"</li>
+ * <li>"yyyy.MM.dd"</li>
+ * <li>"MM/dd/yyyy",</li>
+ * <li>"dd.MM.yyyy"</li>
+ * <li>"EEE MMM dd HH:mm:ss yyyy Z" (DEFAULT)</li>
+ * <li>"EEE MMM dd HH:mm:ss yyyy" (LOCAL)</li>
+ * </ul>
+ *
+ * @param dateStr
+ * the string to be parsed
+ * @return the parsed {@link java.time.LocalDateTime}
+ * @throws java.text.ParseException
+ * if the given dateStr was not recognized
+ */
+ public static LocalDateTime parse(String dateStr) throws ParseException {
+ return parse(dateStr, SystemReader.getInstance().civilNow());
+ }
+
+ /**
+ * Parses a string into a {@link java.time.Instant} using the default
+ * locale. Since this parser also supports relative formats (e.g.
+ * "yesterday") the caller can specify the reference date. These types of
+ * strings can be parsed:
+ * <ul>
+ * <li>"never"</li>
+ * <li>"now"</li>
+ * <li>"yesterday"</li>
+ * <li>"(x) years|months|weeks|days|hours|minutes|seconds ago"<br>
+ * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of '
+ * ' one can use '.' to separate the words</li>
+ * <li>"yyyy-MM-dd HH:mm:ss Z" (ISO)</li>
+ * <li>"EEE, dd MMM yyyy HH:mm:ss Z" (RFC)</li>
+ * <li>"yyyy-MM-dd"</li>
+ * <li>"yyyy.MM.dd"</li>
+ * <li>"MM/dd/yyyy",</li>
+ * <li>"dd.MM.yyyy"</li>
+ * <li>"EEE MMM dd HH:mm:ss yyyy Z" (DEFAULT)</li>
+ * <li>"EEE MMM dd HH:mm:ss yyyy" (LOCAL)</li>
+ * </ul>
+ *
+ * @param dateStr
+ * the string to be parsed
+ * @return the parsed {@link java.time.Instant}
+ * @throws java.text.ParseException
+ * if the given dateStr was not recognized
+ * @since 7.2
+ */
+ public static Instant parseInstant(String dateStr) throws ParseException {
+ return parse(dateStr).atZone(SystemReader.getInstance().getTimeZoneId())
+ .toInstant();
+ }
+
+ // Only tests seem to use this method
+ static LocalDateTime parse(String dateStr, LocalDateTime now)
+ throws ParseException {
+ dateStr = dateStr.trim();
+
+ if (dateStr.equalsIgnoreCase("never")) { //$NON-NLS-1$
+ return LocalDateTime.MAX;
+ }
+ LocalDateTime ret = parseRelative(dateStr, now);
+ if (ret != null) {
+ return ret;
+ }
+ for (ParseableSimpleDateFormat f : ParseableSimpleDateFormat.values()) {
+ try {
+ return parseSimple(dateStr, f);
+ } catch (DateTimeParseException e) {
+ // simply proceed with the next parser
+ }
+ }
+ ParseableSimpleDateFormat[] values = ParseableSimpleDateFormat.values();
+ StringBuilder allFormats = new StringBuilder("\"") //$NON-NLS-1$
+ .append(values[0].formatStr);
+ for (int i = 1; i < values.length; i++) {
+ allFormats.append("\", \"").append(values[i].formatStr); //$NON-NLS-1$
+ }
+ allFormats.append("\""); //$NON-NLS-1$
+ throw new ParseException(
+ MessageFormat.format(JGitText.get().cannotParseDate, dateStr,
+ allFormats.toString()),
+ 0);
+ }
+
+ // tries to parse a string with the formats supported by DateTimeFormatter
+ private static LocalDateTime parseSimple(String dateStr,
+ ParseableSimpleDateFormat f) throws DateTimeParseException {
+ DateTimeFormatter dateFormat = formatCache.computeIfAbsent(f,
+ format -> DateTimeFormatter
+ .ofPattern(f.formatStr)
+ .withLocale(SystemReader.getInstance().getLocale()));
+ TemporalAccessor parsed = dateFormat.parse(dateStr);
+ return parsed.isSupported(ChronoField.HOUR_OF_DAY)
+ ? LocalDateTime.from(parsed)
+ : LocalDate.from(parsed).atStartOfDay();
+ }
+
+ // tries to parse a string with a relative time specification
+ @SuppressWarnings("nls")
+ @Nullable
+ private static LocalDateTime parseRelative(String dateStr,
+ LocalDateTime now) {
+ // check for the static words "yesterday" or "now"
+ if (dateStr.equals("now")) {
+ return now;
+ }
+
+ if (dateStr.equals("yesterday")) {
+ return now.minusDays(1);
+ }
+
+ // parse constructs like "3 days ago", "5.week.2.day.ago"
+ String[] parts = dateStr.split("\\.| ", -1);
+ int partsLength = parts.length;
+ // check we have an odd number of parts (at least 3) and that the last
+ // part is "ago"
+ if (partsLength < 3 || (partsLength & 1) == 0
+ || !parts[parts.length - 1].equals("ago")) {
+ return null;
+ }
+ int number;
+ for (int i = 0; i < parts.length - 2; i += 2) {
+ try {
+ number = Integer.parseInt(parts[i]);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ if (parts[i + 1] == null) {
+ return null;
+ }
+ switch (parts[i + 1]) {
+ case "year":
+ case "years":
+ now = now.minusYears(number);
+ break;
+ case "month":
+ case "months":
+ now = now.minusMonths(number);
+ break;
+ case "week":
+ case "weeks":
+ now = now.minusWeeks(number);
+ break;
+ case "day":
+ case "days":
+ now = now.minusDays(number);
+ break;
+ case "hour":
+ case "hours":
+ now = now.minusHours(number);
+ break;
+ case "minute":
+ case "minutes":
+ now = now.minusMinutes(number);
+ break;
+ case "second":
+ case "seconds":
+ now = now.minusSeconds(number);
+ break;
+ default:
+ return null;
+ }
+ }
+ return now;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
index e3ba606346..1942342c45 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
@@ -321,6 +321,7 @@ public class HttpSupport {
* a {@link org.eclipse.jgit.transport.http.HttpConnection}
* object.
* @throws java.io.IOException
+ * if an IO error occurred
* @since 4.3
*/
public static void disableSslVerify(HttpConnection conn)
@@ -346,7 +347,9 @@ public class HttpSupport {
* that have all available protocols enabled already, up to the one
* specified.
* <p>
+ * <br>
* <table>
+ * <caption>TLS versions</caption>
* <tr>
* <td>SSLContext.getInstance()</td>
* <td>OpenJDK</td>
@@ -354,16 +357,16 @@ public class HttpSupport {
* </tr>
* <tr>
* <td>"TLS"</td>
- * <td>Supported: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)<br />
+ * <td>Supported: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)<br>
* Enabled: TLSv1, TLSV1.1, TLSv1.2 (+ TLSv1.3)</td>
- * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br />
+ * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br>
* Enabled: TLSv1</td>
* </tr>
* <tr>
* <td>"TLSv1.2"</td>
- * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br />
+ * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br>
* Enabled: TLSv1, TLSV1.1, TLSv1.2</td>
- * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br />
+ * <td>Supported: TLSv1, TLSV1.1, TLSv1.2<br>
* Enabled: TLSv1.2</td>
* </tr>
* </table>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java
index 80877bbdc6..8cc5316271 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java
@@ -66,19 +66,7 @@ public class IO {
public static final byte[] readSome(File path, int limit)
throws FileNotFoundException, IOException {
try (SilentFileInputStream in = new SilentFileInputStream(path)) {
- byte[] buf = new byte[limit];
- int cnt = 0;
- for (;;) {
- int n = in.read(buf, cnt, buf.length - cnt);
- if (n <= 0)
- break;
- cnt += n;
- }
- if (cnt == buf.length)
- return buf;
- byte[] res = new byte[cnt];
- System.arraycopy(buf, 0, res, 0, cnt);
- return res;
+ return in.readNBytes(limit);
}
}
@@ -99,37 +87,10 @@ public class IO {
public static final byte[] readFully(File path, int max)
throws FileNotFoundException, IOException {
try (SilentFileInputStream in = new SilentFileInputStream(path)) {
- long sz = Math.max(path.length(), 1);
- if (sz > max)
+ byte[] buf = in.readNBytes(max);
+ if (in.read() != -1) {
throw new IOException(MessageFormat.format(
JGitText.get().fileIsTooLarge, path));
-
- byte[] buf = new byte[(int) sz];
- int valid = 0;
- for (;;) {
- if (buf.length == valid) {
- if (buf.length == max) {
- int next = in.read();
- if (next < 0)
- break;
-
- throw new IOException(MessageFormat.format(
- JGitText.get().fileIsTooLarge, path));
- }
-
- byte[] nb = new byte[Math.min(buf.length * 2, max)];
- System.arraycopy(buf, 0, nb, 0, valid);
- buf = nb;
- }
- int n = in.read(buf, valid, buf.length - valid);
- if (n < 0)
- break;
- valid += n;
- }
- if (valid < buf.length) {
- byte[] nb = new byte[valid];
- System.arraycopy(buf, 0, nb, 0, valid);
- buf = nb;
}
return buf;
}
@@ -157,26 +118,7 @@ public class IO {
*/
public static ByteBuffer readWholeStream(InputStream in, int sizeHint)
throws IOException {
- byte[] out = new byte[sizeHint];
- int pos = 0;
- while (pos < out.length) {
- int read = in.read(out, pos, out.length - pos);
- if (read < 0)
- return ByteBuffer.wrap(out, 0, pos);
- pos += read;
- }
-
- int last = in.read();
- if (last < 0)
- return ByteBuffer.wrap(out, 0, pos);
-
- try (TemporaryBuffer.Heap tmp = new TemporaryBuffer.Heap(
- Integer.MAX_VALUE)) {
- tmp.write(out);
- tmp.write(last);
- tmp.copy(in);
- return ByteBuffer.wrap(tmp.toByteArray());
- }
+ return ByteBuffer.wrap(in.readAllBytes());
}
/**
@@ -197,13 +139,9 @@ public class IO {
*/
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(JGitText.get().shortReadOfBlock);
- off += r;
- len -= r;
- }
+ int read = fd.readNBytes(dst, off, len);
+ if (read != len)
+ throw new EOFException(JGitText.get().shortReadOfBlock);
}
/**
@@ -271,14 +209,7 @@ public class IO {
*/
public static int readFully(InputStream fd, byte[] dst, int off)
throws IOException {
- int r;
- int len = 0;
- while (off < dst.length
- && (r = fd.read(dst, off, dst.length - off)) >= 0) {
- off += r;
- len += r;
- }
- return len;
+ return fd.readNBytes(dst, off, dst.length - off);
}
/**
@@ -300,6 +231,7 @@ public class IO {
*/
public static void skipFully(InputStream fd, long toSkip)
throws IOException {
+ // same as fd.skipNBytes(toSkip) of JDK 12;
while (toSkip > 0) {
final long r = fd.skip(toSkip);
if (r <= 0)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java
index cc4f0a46fe..6a5190c6a2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/IntList.java
@@ -204,7 +204,6 @@ public class IntList {
entries = n;
}
- /** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder r = new StringBuilder();
@@ -220,6 +219,8 @@ public class IntList {
/**
* A comparator of primitive ints.
+ *
+ * @since 6.6
*/
public interface IntComparator {
@@ -230,8 +231,8 @@ public class IntList {
* the first int to compare
* @param second
* the second int to compare
- * @return a negative number if first < second, 0 if first == second, or
- * a positive number if first > second
+ * @return a negative number if first &lt; second, 0 if first == second, or
+ * a positive number if first &gt; second
*/
int compare(int first, int second);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Iterators.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Iterators.java
new file mode 100644
index 0000000000..74b728bdf7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Iterators.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2025, NVIDIA Corporation.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.util;
+
+import java.util.Iterator;
+
+/**
+ * Utility class for Iterators
+ *
+ * @since 6.10.2
+ */
+public class Iterators {
+ /**
+ * Create an iterator which traverses an array in reverse.
+ *
+ * @param array T[]
+ * @return Iterator<T>
+ */
+ public static <T> Iterator<T> reverseIterator(T[] array) {
+ return new Iterator<>() {
+ int index = array.length;
+
+ @Override
+ public boolean hasNext() {
+ return index > 0;
+ }
+
+ @Override
+ public T next() {
+ return array[--index];
+ }
+ };
+ }
+
+ /**
+ * Make an iterable for easy use in modern for loops.
+ *
+ * @param iterator Iterator<T>
+ * @return Iterable<T>
+ */
+ public static <T> Iterable<T> iterable(Iterator<T> iterator) {
+ return new Iterable<>() {
+ @Override
+ public Iterator<T> iterator() {
+ return iterator;
+ }
+ };
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java
index 7456c71f5f..7b7c1d0886 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java
@@ -43,6 +43,8 @@ public class LfsFactory {
}
/**
+ * Get the LFS factory instance
+ *
* @return the current LFS implementation
*/
public static LfsFactory getInstance() {
@@ -50,6 +52,8 @@ public class LfsFactory {
}
/**
+ * Set the LFS factory instance
+ *
* @param instance
* register a {@link LfsFactory} instance as the
* {@link LfsFactory} implementation to use.
@@ -59,6 +63,8 @@ public class LfsFactory {
}
/**
+ * Whether LFS support is available
+ *
* @return whether LFS support is available
*/
public boolean isAvailable() {
@@ -105,6 +111,7 @@ public class LfsFactory {
* @return a loader for the actual data of a blob, or the original loader in
* case LFS is not applicable.
* @throws IOException
+ * if an IO error occurred
*/
public ObjectLoader applySmudgeFilter(Repository db,
ObjectLoader loader, Attribute attribute) throws IOException {
@@ -117,6 +124,7 @@ public class LfsFactory {
* @param repo
* the {@link Repository} the hook is applied to.
* @param outputStream
+ * output stream
* @return a {@link PrePushHook} implementation or <code>null</code>
*/
@Nullable
@@ -131,7 +139,9 @@ public class LfsFactory {
* @param repo
* the {@link Repository} the hook is applied to.
* @param outputStream
+ * output stream
* @param errorStream
+ * error stream
* @return a {@link PrePushHook} implementation or <code>null</code>
* @since 5.6
*/
@@ -153,6 +163,8 @@ public class LfsFactory {
}
/**
+ * Whether LFS is enabled
+ *
* @param db
* the repository to check
* @return whether LFS is enabled for the given repository locally or
@@ -163,6 +175,8 @@ public class LfsFactory {
}
/**
+ * Get git attributes for given path
+ *
* @param db
* the repository
* @param path
@@ -285,6 +299,8 @@ public class LfsFactory {
}
/**
+ * Get stream length
+ *
* @return the length of the stream
*/
public long getLength() {
@@ -298,6 +314,8 @@ public class LfsFactory {
*/
public interface LfsInstallCommand extends Callable<Void> {
/**
+ * Set the repository to enable LFS for
+ *
* @param repo
* the repository to enable support for.
* @return The {@link LfsInstallCommand} for chaining.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java
index b2bdfc1fd7..47f38f4627 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/LongList.java
@@ -140,7 +140,6 @@ public class LongList {
entries = n;
}
- /** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder r = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java
index 7b4ff7ff16..fea7172d84 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/NB.java
@@ -133,7 +133,7 @@ public final class NB {
* @since 3.0
*/
public static long decodeInt64(final byte[] intbuf, final int offset) {
- long r = intbuf[offset] << 8;
+ long r = (long) intbuf[offset] << 8;
r |= intbuf[offset + 1] & 0xff;
r <<= 8;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java
index 3de7a1587c..be4bd9e356 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawCharSequence.java
@@ -41,25 +41,21 @@ public final class RawCharSequence implements CharSequence {
endPtr = end;
}
- /** {@inheritDoc} */
@Override
public char charAt(int index) {
return (char) (buffer[startPtr + index] & 0xff);
}
- /** {@inheritDoc} */
@Override
public int length() {
return endPtr - startPtr;
}
- /** {@inheritDoc} */
@Override
public CharSequence subSequence(int start, int end) {
return new RawCharSequence(buffer, startPtr + start, startPtr + end);
}
- /** {@inheritDoc} */
@Override
public String toString() {
final int n = length();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
index 0e8e9b3d84..3ed72516c7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
@@ -13,10 +13,17 @@ package org.eclipse.jgit.util;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.time.Instant.EPOCH;
+import static java.time.ZoneOffset.UTC;
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.object;
+import static org.eclipse.jgit.lib.ObjectChecker.parent;
+import static org.eclipse.jgit.lib.ObjectChecker.tag;
import static org.eclipse.jgit.lib.ObjectChecker.tagger;
+import static org.eclipse.jgit.lib.ObjectChecker.tree;
+import static org.eclipse.jgit.lib.ObjectChecker.type;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
@@ -25,6 +32,10 @@ import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -39,14 +50,6 @@ import org.eclipse.jgit.lib.PersonIdent;
* Handy utility functions to parse raw object contents.
*/
public final class RawParseUtils {
- /**
- * UTF-8 charset constant.
- *
- * @since 2.2
- * @deprecated use {@link java.nio.charset.StandardCharsets#UTF_8} instead
- */
- @Deprecated
- public static final Charset UTF8_CHARSET = UTF_8;
private static final byte[] digits10;
@@ -354,6 +357,7 @@ public final class RawParseUtils {
* if the string is not hex formatted.
* @since 4.3
*/
+ @SuppressWarnings("IntLongMath")
public static final long parseHexInt64(final byte[] bs, final int p) {
long r = digits16[bs[p]] << 4;
@@ -461,6 +465,29 @@ public final class RawParseUtils {
}
/**
+ * Parse a Git style timezone string in [+-]hhmm format
+ *
+ * @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 ZoneOffset represention of the timezone offset string.
+ * Invalid offsets default to UTC.
+ */
+ private static ZoneId parseZoneOffset(final byte[] b, int ptr,
+ MutableInteger ptrResult) {
+ int hhmm = parseBase10(b, ptr, ptrResult);
+ try {
+ return ZoneOffset.ofHoursMinutes(hhmm / 100, hhmm % 100);
+ } catch (DateTimeException e) {
+ return UTC;
+ }
+ }
+
+ /**
* Locate the first position after a given character.
*
* @param b
@@ -519,17 +546,24 @@ public final class RawParseUtils {
}
/**
- * Locate the end of the header. Note that headers may be
- * more than one line long.
+ * Locate the first end of line after the given position, while treating
+ * following lines which are starting with spaces as part of the current
+ * line.
+ * <p>
+ * For example, {@code nextLfSkippingSplitLines(
+ * "row \n with space at beginning of a following line\nThe actual next line",
+ * 0)} will return the position of {@code "\nThe actual next line"}.
+ *
* @param b
* buffer to scan.
* @param ptr
- * position within buffer to start looking for the end-of-header.
- * @return new position just after the header. This is either
- * b.length, or the index of the header's terminating newline.
- * @since 5.1
+ * position within buffer to start looking for the next line.
+ * @return new position just after the line end of the last line-split. This
+ * is either b.length, or the index of the current split-line's
+ * terminating newline.
+ * @since 6.9
*/
- public static final int headerEnd(final byte[] b, int ptr) {
+ public static final int nextLfSkippingSplitLines(final byte[] b, int ptr) {
final int sz = b.length;
while (ptr < sz) {
final byte c = b[ptr++];
@@ -537,7 +571,62 @@ public final class RawParseUtils {
return ptr - 1;
}
}
- return ptr - 1;
+ return ptr;
+ }
+
+ /**
+ * Extract a part of a buffer as a header value, removing the single blanks
+ * at the front of continuation lines.
+ *
+ * @param b
+ * buffer to extract the header from
+ * @param start
+ * of the header value, see
+ * {@link #headerStart(byte[], byte[], int)}
+ * @param end
+ * of the header; see
+ * {@link #nextLfSkippingSplitLines(byte[], int)}
+ * @return the header value, with blanks indicating continuation lines
+ * stripped
+ * @since 6.9
+ */
+ public static final byte[] headerValue(final byte[] b, int start, int end) {
+ byte[] data = new byte[end - start];
+ int out = 0;
+ byte last = '\0';
+ for (int in = start; in < end; in++) {
+ byte ch = b[in];
+ if (ch != ' ' || last != '\n') {
+ data[out++] = ch;
+ }
+ last = ch;
+ }
+ if (out == data.length) {
+ return data;
+ }
+ return Arrays.copyOf(data, out);
+ }
+
+ /**
+ * Locate the first end of header after the given position. Note that
+ * headers may be more than one line long.
+ * <p>
+ * Also note that there might be multiple headers. If you wish to find the
+ * last header's end - call this in a loop.
+ *
+ * @param b
+ * buffer to scan.
+ * @param ptr
+ * position within buffer to start looking for the header
+ * (normally a new-line).
+ * @return new position just after the line end. This is either b.length, or
+ * the index of the header's terminating newline.
+ * @since 5.1
+ * @deprecated use {{@link #nextLfSkippingSplitLines}} directly instead
+ */
+ @Deprecated
+ public static final int headerEnd(final byte[] b, int ptr) {
+ return nextLfSkippingSplitLines(b, ptr);
}
/**
@@ -575,6 +664,22 @@ public final class RawParseUtils {
}
/**
+ * Returns whether the message starts with any known headers.
+ *
+ * @param b
+ * buffer to scan.
+ * @return whether the message starts with any known headers
+ * @since 6.9
+ */
+ public static final boolean hasAnyKnownHeaders(byte[] b) {
+ return match(b, 0, tree) != -1 || match(b, 0, parent) != -1
+ || match(b, 0, author) != -1 || match(b, 0, committer) != -1
+ || match(b, 0, encoding) != -1 || match(b, 0, object) != -1
+ || match(b, 0, type) != -1 || match(b, 0, tag) != -1
+ || match(b, 0, tagger) != -1;
+ }
+
+ /**
* Locate the first position before a given character.
*
* @param b
@@ -868,6 +973,26 @@ public final class RawParseUtils {
}
/**
+ * Parse the "encoding " header into a character set reference.
+ * <p>
+ * If unsuccessful, return UTF-8.
+ *
+ * @param buffer
+ * buffer to scan.
+ * @return the Java character set representation. Never null. Default to
+ * UTF-8.
+ * @see #parseEncoding(byte[])
+ * @since 6.7
+ */
+ public static Charset guessEncoding(byte[] buffer) {
+ try {
+ return parseEncoding(buffer);
+ } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
+ return UTF_8;
+ }
+ }
+
+ /**
* Parse a name string (e.g. author, committer, tagger) into a PersonIdent.
* <p>
* Leading spaces won't be trimmed from the string, i.e. will show up in the
@@ -931,17 +1056,19 @@ public final class RawParseUtils {
// character if there is no trailing LF.
final int tzBegin = lastIndexOfTrim(raw, ' ',
nextLF(raw, emailE - 1) - 2) + 1;
- if (tzBegin <= emailE) // No time/zone, still valid
- return new PersonIdent(name, email, 0, 0);
+ if (tzBegin <= emailE) { // No time/zone, still valid
+ return new PersonIdent(name, email, EPOCH, UTC);
+ }
final int whenBegin = Math.max(emailE,
lastIndexOfTrim(raw, ' ', tzBegin - 1) + 1);
- if (whenBegin >= tzBegin - 1) // No time/zone, still valid
- return new PersonIdent(name, email, 0, 0);
+ if (whenBegin >= tzBegin - 1) { // No time/zone, still valid
+ return new PersonIdent(name, email, EPOCH, UTC);
+ }
- final long when = parseLongBase10(raw, whenBegin, null);
- final int tz = parseTimeZoneOffset(raw, tzBegin);
- return new PersonIdent(name, email, when * 1000L, tz);
+ long when = parseLongBase10(raw, whenBegin, null);
+ return new PersonIdent(name, email, Instant.ofEpochSecond(when),
+ parseZoneOffset(raw, tzBegin, null));
}
/**
@@ -979,16 +1106,16 @@ public final class RawParseUtils {
name = decode(raw, nameB, stop);
final MutableInteger ptrout = new MutableInteger();
- long when;
- int tz;
+ Instant when;
+ ZoneId tz;
if (emailE < stop) {
- when = parseLongBase10(raw, emailE + 1, ptrout);
- tz = parseTimeZoneOffset(raw, ptrout.value);
+ when = Instant.ofEpochSecond(parseLongBase10(raw, emailE + 1, ptrout));
+ tz = parseZoneOffset(raw, ptrout.value, null);
} else {
- when = 0;
- tz = 0;
+ when = EPOCH;
+ tz = UTC;
}
- return new PersonIdent(name, email, when * 1000L, tz);
+ return new PersonIdent(name, email, when, tz);
}
/**
@@ -1237,6 +1364,7 @@ public final class RawParseUtils {
final int sz = b.length;
if (ptr == 0)
ptr += 48; // skip the "object ..." line.
+ // Assume the rest of the current paragraph is all headers.
while (ptr < sz && b[ptr] != '\n')
ptr = nextLF(b, ptr);
if (ptr < sz && b[ptr] == '\n')
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java
index 57464f3c41..04fdcd0fa4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawSubStringPattern.java
@@ -98,7 +98,6 @@ public class RawSubStringPattern {
return needleString;
}
- /** {@inheritDoc} */
@Override
public String toString() {
return pattern();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
index 462bab081a..350ec76cf9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefList.java
@@ -43,6 +43,8 @@ public class RefList<T extends Ref> implements Iterable<Ref> {
/**
* Create an empty unmodifiable reference list.
*
+ * @param <T>
+ * type of reference being stored.
* @return an empty unmodifiable reference list.
*/
@SuppressWarnings("unchecked")
@@ -70,7 +72,6 @@ public class RefList<T extends Ref> implements Iterable<Ref> {
this.cnt = src.cnt;
}
- /** {@inheritDoc} */
@Override
public Iterator<Ref> iterator() {
return new Iterator<>() {
@@ -286,7 +287,6 @@ public class RefList<T extends Ref> implements Iterable<Ref> {
return add(idx, ref);
}
- /** {@inheritDoc} */
@Override
public String toString() {
StringBuilder r = new StringBuilder();
@@ -305,6 +305,8 @@ public class RefList<T extends Ref> implements Iterable<Ref> {
/**
* Create a {@link Collector} for {@link Ref}.
*
+ * @param <T>
+ * type of reference being stored.
* @param mergeFunction
* if specified the result will be sorted and deduped.
* @return {@link Collector} for {@link Ref}
@@ -355,7 +357,11 @@ public class RefList<T extends Ref> implements Iterable<Ref> {
list = new Ref[Math.max(capacity, 16)];
}
- /** @return number of items in this builder's internal collection. */
+ /**
+ * Get size
+ *
+ * @return number of items in this builder's internal collection.
+ */
public int size() {
return size;
}
@@ -390,6 +396,7 @@ public class RefList<T extends Ref> implements Iterable<Ref> {
* after additions are complete using {@link #sort()}.
*
* @param ref
+ * reference to add
*/
public void add(T ref) {
if (list.length == size) {
@@ -404,6 +411,7 @@ public class RefList<T extends Ref> implements Iterable<Ref> {
* Add all items from another builder.
*
* @param other
+ * another builder
* @since 5.4
*/
public void addAll(Builder other) {
@@ -454,6 +462,7 @@ public class RefList<T extends Ref> implements Iterable<Ref> {
* Dedupe the refs in place. Must be called after {@link #sort}.
*
* @param mergeFunction
+ * function used for de-duplication
*/
@SuppressWarnings("unchecked")
void dedupe(BinaryOperator<T> mergeFunction) {
@@ -475,7 +484,11 @@ public class RefList<T extends Ref> implements Iterable<Ref> {
Arrays.fill(list, size, list.length, null);
}
- /** @return an unmodifiable list using this collection's backing array. */
+ /**
+ * Get unmodifiable list based on this list
+ *
+ * @return an unmodifiable list using this collection's backing array.
+ */
public RefList<T> toRefList() {
return new RefList<>(list, size);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java
index c68a76cef4..a4d1fd5b70 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java
@@ -119,13 +119,11 @@ public class RefMap extends AbstractMap<String, Ref> {
this.resolved = (RefList<Ref>) resolved;
}
- /** {@inheritDoc} */
@Override
public boolean containsKey(Object name) {
return get(name) != null;
}
- /** {@inheritDoc} */
@Override
public Ref get(Object key) {
String name = toRefName((String) key);
@@ -137,7 +135,6 @@ public class RefMap extends AbstractMap<String, Ref> {
return ref;
}
- /** {@inheritDoc} */
@Override
public Ref put(String keyName, Ref value) {
String name = toRefName(keyName);
@@ -165,7 +162,6 @@ public class RefMap extends AbstractMap<String, Ref> {
return prior;
}
- /** {@inheritDoc} */
@Override
public Ref remove(Object key) {
String name = toRefName((String) key);
@@ -189,13 +185,11 @@ public class RefMap extends AbstractMap<String, Ref> {
return res;
}
- /** {@inheritDoc} */
@Override
public boolean isEmpty() {
return entrySet().isEmpty();
}
- /** {@inheritDoc} */
@Override
public Set<Entry<String, Ref>> entrySet() {
if (entrySet == null) {
@@ -238,7 +232,6 @@ public class RefMap extends AbstractMap<String, Ref> {
return entrySet;
}
- /** {@inheritDoc} */
@Override
public String toString() {
StringBuilder r = new StringBuilder();
@@ -259,6 +252,7 @@ public class RefMap extends AbstractMap<String, Ref> {
* Create a {@link Collector} for {@link Ref}.
*
* @param mergeFunction
+ * merge function
* @return {@link Collector} for {@link Ref}
* @since 5.4
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java
index 5611b1e78e..b6b19e0e7b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java
@@ -10,6 +10,8 @@
package org.eclipse.jgit.util;
import java.text.MessageFormat;
+import java.time.Duration;
+import java.time.Instant;
import java.util.Date;
import org.eclipse.jgit.internal.JGitText;
@@ -42,12 +44,29 @@ public class RelativeDateFormatter {
* @return age of given {@link java.util.Date} compared to now formatted in
* the same relative format as returned by
* {@code git log --relative-date}
+ * @deprecated Use {@link #format(Instant)} instead.
*/
+ @Deprecated(since = "7.2")
@SuppressWarnings("boxing")
public static String format(Date when) {
+ return format(when.toInstant());
+ }
- long ageMillis = SystemReader.getInstance().getCurrentTime()
- - when.getTime();
+ /**
+ * Get age of given {@link java.time.Instant} compared to now formatted in the
+ * same relative format as returned by {@code git log --relative-date}
+ *
+ * @param when
+ * an instant to format
+ * @return age of given instant compared to now formatted in
+ * the same relative format as returned by
+ * {@code git log --relative-date}
+ * @since 7.2
+ */
+ @SuppressWarnings("boxing")
+ public static String format(Instant when) {
+ long ageMillis = Duration
+ .between(when, SystemReader.getInstance().now()).toMillis();
// shouldn't happen in a perfect world
if (ageMillis < 0)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java
index cf06172c17..e3e3e04fd9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SignatureUtils.java
@@ -13,8 +13,8 @@ import java.text.MessageFormat;
import java.util.Locale;
import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.GpgSignatureVerifier.SignatureVerification;
-import org.eclipse.jgit.lib.GpgSignatureVerifier.TrustLevel;
+import org.eclipse.jgit.lib.SignatureVerifier.SignatureVerification;
+import org.eclipse.jgit.lib.SignatureVerifier.TrustLevel;
import org.eclipse.jgit.lib.PersonIdent;
/**
@@ -39,29 +39,34 @@ public final class SignatureUtils {
* to use for dates
* @return a textual representation of the {@link SignatureVerification},
* using LF as line separator
+ *
+ * @since 7.0
*/
public static String toString(SignatureVerification verification,
PersonIdent creator, GitDateFormatter formatter) {
StringBuilder result = new StringBuilder();
- // Use the creator's timezone for the signature date
- PersonIdent dateId = new PersonIdent(creator,
- verification.getCreationDate());
- result.append(MessageFormat.format(JGitText.get().verifySignatureMade,
- formatter.formatDate(dateId)));
- result.append('\n');
+ if (verification.creationDate() != null) {
+ // Use the creator's timezone for the signature date
+ PersonIdent dateId = new PersonIdent(creator,
+ verification.creationDate().toInstant());
+ result.append(
+ MessageFormat.format(JGitText.get().verifySignatureMade,
+ formatter.formatDate(dateId)));
+ result.append('\n');
+ }
result.append(MessageFormat.format(
JGitText.get().verifySignatureKey,
- verification.getKeyFingerprint().toUpperCase(Locale.ROOT)));
+ verification.keyFingerprint().toUpperCase(Locale.ROOT)));
result.append('\n');
- if (!StringUtils.isEmptyOrNull(verification.getSigner())) {
+ if (!StringUtils.isEmptyOrNull(verification.signer())) {
result.append(
MessageFormat.format(JGitText.get().verifySignatureIssuer,
- verification.getSigner()));
+ verification.signer()));
result.append('\n');
}
String msg;
- if (verification.getVerified()) {
- if (verification.isExpired()) {
+ if (verification.verified()) {
+ if (verification.expired()) {
msg = JGitText.get().verifySignatureExpired;
} else {
msg = JGitText.get().verifySignatureGood;
@@ -69,14 +74,14 @@ public final class SignatureUtils {
} else {
msg = JGitText.get().verifySignatureBad;
}
- result.append(MessageFormat.format(msg, verification.getKeyUser()));
- if (!TrustLevel.UNKNOWN.equals(verification.getTrustLevel())) {
+ result.append(MessageFormat.format(msg, verification.keyUser()));
+ if (!TrustLevel.UNKNOWN.equals(verification.trustLevel())) {
result.append(' ' + MessageFormat
.format(JGitText.get().verifySignatureTrust, verification
- .getTrustLevel().name().toLowerCase(Locale.ROOT)));
+ .trustLevel().name().toLowerCase(Locale.ROOT)));
}
result.append('\n');
- msg = verification.getMessage();
+ msg = verification.message();
if (!StringUtils.isEmptyOrNull(msg)) {
result.append(msg);
result.append('\n');
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java
index e29704158d..42a76b5b16 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java
@@ -48,6 +48,7 @@ public class SshSupport {
* cases.
* @return The entire output read from stdout.
* @throws IOException
+ * if an IO error occurred
* @throws CommandFailedException
* if the ssh command execution failed, error message contains
* the content of stderr.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java
index d957deb34c..efa6e7ddc3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Stats.java
@@ -43,14 +43,18 @@ public class Stats {
}
/**
- * @return number of the added values
+ * Returns the number of added values
+ *
+ * @return the number of added values
*/
public int count() {
return n;
}
/**
- * @return minimum of the added values
+ * Returns the smallest value added
+ *
+ * @return the smallest value added
*/
public double min() {
if (n < 1) {
@@ -60,7 +64,9 @@ public class Stats {
}
/**
- * @return maximum of the added values
+ * Returns the biggest value added
+ *
+ * @return the biggest value added
*/
public double max() {
if (n < 1) {
@@ -70,9 +76,10 @@ public class Stats {
}
/**
- * @return average of the added values
+ * Returns the average of the added values
+ *
+ * @return the average of the added values
*/
-
public double avg() {
if (n < 1) {
return Double.NaN;
@@ -81,7 +88,9 @@ public class Stats {
}
/**
- * @return variance of the added values
+ * Returns the variance of the added values
+ *
+ * @return the variance of the added values
*/
public double var() {
if (n < 2) {
@@ -91,7 +100,9 @@ public class Stats {
}
/**
- * @return standard deviation of the added values
+ * Returns the standard deviation of the added values
+ *
+ * @return the standard deviation of the added values
*/
public double stddev() {
return Math.sqrt(this.var());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
index 917add3609..e381a3bcc9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/StringUtils.java
@@ -14,6 +14,7 @@ import java.text.MessageFormat;
import java.util.Collection;
import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
@@ -22,6 +23,8 @@ import org.eclipse.jgit.lib.Constants;
*/
public final class StringUtils {
+ private static final String EMPTY = ""; //$NON-NLS-1$
+
private static final long KiB = 1024;
private static final long MiB = 1024 * KiB;
@@ -181,9 +184,9 @@ public final class StringUtils {
*
* @param stringValue
* the string to parse.
- * @return the boolean interpretation of {@code value}.
+ * @return the boolean interpretation of {@code stringValue}.
* @throws java.lang.IllegalArgumentException
- * if {@code value} is not recognized as one of the standard
+ * if {@code stringValue} is not recognized as one of the standard
* boolean names.
*/
public static boolean toBoolean(String stringValue) {
@@ -275,6 +278,44 @@ public final class StringUtils {
}
/**
+ * Remove the specified character from beginning and end of a string
+ * <p>
+ * If the character repeats, all copies
+ *
+ * @param str input string
+ * @param c character to remove
+ * @return the input string with c
+ * @since 7.2
+ */
+ public static String trim(String str, char c) {
+ if (str == null || str.length() == 0) {
+ return str;
+ }
+
+ int endPos = str.length()-1;
+ while (endPos >= 0 && str.charAt(endPos) == c) {
+ endPos--;
+ }
+
+ // Whole string is c
+ if (endPos == -1) {
+ return EMPTY;
+ }
+
+ int startPos = 0;
+ while (startPos < endPos && str.charAt(startPos) == c) {
+ startPos++;
+ }
+
+ if (startPos == 0 && endPos == str.length()-1) {
+ // No need to copy
+ return str;
+ }
+
+ return str.substring(startPos, endPos+1);
+ }
+
+ /**
* Appends {@link Constants#DOT_GIT_EXT} unless the given name already ends
* with that suffix.
*
@@ -346,7 +387,7 @@ public final class StringUtils {
* allow negative numbers, too
* @return the value parsed
* @throws NumberFormatException
- * if the {@value} is not parseable, or beyond the range of
+ * if the {@code value} is not parseable, or beyond the range of
* {@link Long}
* @throws StringIndexOutOfBoundsException
* if the string is empty or contains only whitespace, or
@@ -420,7 +461,7 @@ public final class StringUtils {
* allow negative numbers, too
* @return the value parsed
* @throws NumberFormatException
- * if the {@value} is not parseable or beyond the range of
+ * if the {@code value} is not parseable or beyond the range of
* {@link Integer}
* @throws StringIndexOutOfBoundsException
* if the string is empty or contains only whitespace, or
@@ -463,4 +504,39 @@ public final class StringUtils {
}
return String.valueOf(value);
}
+
+ /**
+ * Compares Strings and returns the initial sequence of characters that is
+ * common to all of them.
+ *
+ * @param strings
+ * Strings to consider
+ * @return common prefix of all Strings
+ * @since 6.8
+ */
+ public static @NonNull String commonPrefix(@Nullable String... strings) {
+ if (strings == null || strings.length == 0) {
+ return EMPTY;
+ }
+ String first = strings[0];
+ if (first == null) {
+ return EMPTY;
+ }
+ if (strings.length == 1) {
+ return first;
+ }
+ for (int i = 0; i < first.length(); i++) {
+ char currentChar = first.charAt(i);
+ for (int j = 1; j < strings.length; j++) {
+ String str = strings[j];
+ if (str == null) {
+ return EMPTY;
+ }
+ if (str.length() == i || currentChar != str.charAt(i)) {
+ return str.substring(0, i);
+ }
+ }
+ }
+ return first;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
index a8a77904a2..0b7c6204f2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
@@ -23,10 +23,12 @@ import java.nio.charset.UnsupportedCharsetException;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicReference;
@@ -39,6 +41,7 @@ import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectChecker;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.storage.file.UserConfigFile;
import org.eclipse.jgit.util.time.MonotonicClock;
import org.eclipse.jgit.util.time.MonotonicSystemClock;
import org.slf4j.Logger;
@@ -124,28 +127,20 @@ public abstract class SystemReader {
@Override
public FileBasedConfig openUserConfig(Config parent, FS fs) {
- return new FileBasedConfig(parent, new File(fs.userHome(), ".gitconfig"), //$NON-NLS-1$
- fs);
- }
-
- private Path getXDGConfigHome(FS fs) {
- String configHomePath = getenv(Constants.XDG_CONFIG_HOME);
- if (StringUtils.isEmptyOrNull(configHomePath)) {
- configHomePath = new File(fs.userHome(), ".config") //$NON-NLS-1$
- .getAbsolutePath();
- }
- try {
- return Paths.get(configHomePath);
- } catch (InvalidPathException e) {
- LOG.error(JGitText.get().logXDGConfigHomeInvalid,
- configHomePath, e);
+ File homeFile = new File(fs.userHome(), ".gitconfig"); //$NON-NLS-1$
+ Path xdgPath = getXdgConfigDirectory(fs);
+ if (xdgPath != null) {
+ Path configPath = xdgPath.resolve("git") //$NON-NLS-1$
+ .resolve(Constants.CONFIG);
+ return new UserConfigFile(parent, homeFile, configPath.toFile(),
+ fs);
}
- return null;
+ return new FileBasedConfig(parent, homeFile, fs);
}
@Override
public FileBasedConfig openJGitConfig(Config parent, FS fs) {
- Path xdgPath = getXDGConfigHome(fs);
+ Path xdgPath = getXdgConfigDirectory(fs);
if (xdgPath != null) {
Path configPath = xdgPath.resolve("jgit") //$NON-NLS-1$
.resolve(Constants.CONFIG);
@@ -176,11 +171,87 @@ public abstract class SystemReader {
}
@Override
+ public Instant now() {
+ return Instant.now();
+ }
+
+ @Override
public int getTimezone(long when) {
return getTimeZone().getOffset(when) / (60 * 1000);
}
}
+ /**
+ * Delegating SystemReader. Reduces boiler-plate code applications need to
+ * implement when overriding only a few of the SystemReader's methods.
+ *
+ * @since 6.9
+ */
+ public static class Delegate extends SystemReader {
+
+ private final SystemReader delegate;
+
+ /**
+ * Create a delegating system reader
+ *
+ * @param delegate
+ * the system reader to delegate to
+ */
+ public Delegate(SystemReader delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String getHostname() {
+ return delegate.getHostname();
+ }
+
+ @Override
+ public String getenv(String variable) {
+ return delegate.getenv(variable);
+ }
+
+ @Override
+ public String getProperty(String key) {
+ return delegate.getProperty(key);
+ }
+
+ @Override
+ public FileBasedConfig openUserConfig(Config parent, FS fs) {
+ return delegate.openUserConfig(parent, fs);
+ }
+
+ @Override
+ public FileBasedConfig openSystemConfig(Config parent, FS fs) {
+ return delegate.openSystemConfig(parent, fs);
+ }
+
+ @Override
+ public FileBasedConfig openJGitConfig(Config parent, FS fs) {
+ return delegate.openJGitConfig(parent, fs);
+ }
+
+ @Override
+ public long getCurrentTime() {
+ return delegate.getCurrentTime();
+ }
+
+ @Override
+ public Instant now() {
+ return delegate.now();
+ }
+
+ @Override
+ public int getTimezone(long when) {
+ return delegate.getTimezone(when);
+ }
+
+ @Override
+ public ZoneOffset getTimeZoneAt(Instant when) {
+ return delegate.getTimeZoneAt(when);
+ }
+ }
+
private static volatile SystemReader INSTANCE = DEFAULT;
/**
@@ -391,6 +462,66 @@ public abstract class SystemReader {
}
/**
+ * Gets the directory denoted by environment variable XDG_CONFIG_HOME. If
+ * the variable is not set or empty, return a path for
+ * {@code $HOME/.config}.
+ *
+ * @param fileSystem
+ * {@link FS} to get the user's home directory
+ * @return a {@link Path} denoting the directory, which may exist or not, or
+ * {@code null} if the environment variable is not set and there is
+ * no home directory, or the path is invalid.
+ * @since 6.7
+ */
+ public Path getXdgConfigDirectory(FS fileSystem) {
+ String configHomePath = getenv(Constants.XDG_CONFIG_HOME);
+ if (StringUtils.isEmptyOrNull(configHomePath)) {
+ File home = fileSystem.userHome();
+ if (home == null) {
+ return null;
+ }
+ configHomePath = new File(home, ".config").getAbsolutePath(); //$NON-NLS-1$
+ }
+ try {
+ return Paths.get(configHomePath);
+ } catch (InvalidPathException e) {
+ LOG.error(JGitText.get().logXDGConfigHomeInvalid, configHomePath,
+ e);
+ }
+ return null;
+ }
+
+ /**
+ * Gets the directory denoted by environment variable XDG_CACHE_HOME. If
+ * the variable is not set or empty, return a path for
+ * {@code $HOME/.cache}.
+ *
+ * @param fileSystem
+ * {@link FS} to get the user's home directory
+ * @return a {@link Path} denoting the directory, which may exist or not, or
+ * {@code null} if the environment variable is not set and there is
+ * no home directory, or the path is invalid.
+ * @since 7.3
+ */
+ public Path getXdgCacheDirectory(FS fileSystem) {
+ String cacheHomePath = getenv(Constants.XDG_CACHE_HOME);
+ if (StringUtils.isEmptyOrNull(cacheHomePath)) {
+ File home = fileSystem.userHome();
+ if (home == null) {
+ return null;
+ }
+ cacheHomePath = new File(home, ".cache").getAbsolutePath(); //$NON-NLS-1$
+ }
+ try {
+ return Paths.get(cacheHomePath);
+ } catch (InvalidPathException e) {
+ LOG.error(JGitText.get().logXDGCacheHomeInvalid, cacheHomePath,
+ e);
+ }
+ return null;
+ }
+
+ /**
* Update config and its parents if they seem modified
*
* @param config
@@ -419,10 +550,37 @@ public abstract class SystemReader {
* Get the current system time
*
* @return the current system time
+ *
+ * @deprecated Use {@link #now()}
*/
+ @Deprecated(since = "7.1")
public abstract long getCurrentTime();
/**
+ * Get the current system time
+ *
+ * @return the current system time
+ *
+ * @since 7.1
+ */
+ public Instant now() {
+ // Subclasses overriding getCurrentTime should keep working
+ // TODO(ifrade): Once we remove getCurrentTime, use Instant.now()
+ return Instant.ofEpochMilli(getCurrentTime());
+ }
+
+ /**
+ * Get "now" as civil time, in the System timezone
+ *
+ * @return the current system time
+ *
+ * @since 7.1
+ */
+ public LocalDateTime civilNow() {
+ return LocalDateTime.ofInstant(now(), getTimeZoneId());
+ }
+
+ /**
* Get clock instance preferred by this system.
*
* @return clock instance preferred by this system.
@@ -438,20 +596,48 @@ public abstract class SystemReader {
* @param when
* a system timestamp
* @return the local time zone
+ *
+ * @deprecated Use {@link #getTimeZoneAt(Instant)} instead.
*/
+ @Deprecated(since = "7.1")
public abstract int getTimezone(long when);
/**
+ * Get the local time zone offset at "when" time
+ *
+ * @param when
+ * a system timestamp
+ * @return the local time zone
+ * @since 7.1
+ */
+ public ZoneOffset getTimeZoneAt(Instant when) {
+ return getTimeZoneId().getRules().getOffset(when);
+ }
+
+ /**
* Get system time zone, possibly mocked for testing
*
* @return system time zone, possibly mocked for testing
* @since 1.2
+ *
+ * @deprecated Use {@link #getTimeZoneId()}
*/
+ @Deprecated(since = "7.1")
public TimeZone getTimeZone() {
return TimeZone.getDefault();
}
/**
+ * Get system time zone, possibly mocked for testing
+ *
+ * @return system time zone, possibly mocked for testing
+ * @since 7.1
+ */
+ public ZoneId getTimeZoneId() {
+ return ZoneId.systemDefault();
+ }
+
+ /**
* Get the locale to use
*
* @return the locale to use
@@ -586,9 +772,7 @@ public abstract class SystemReader {
}
private String getOsName() {
- return AccessController.doPrivileged(
- (PrivilegedAction<String>) () -> getProperty("os.name") //$NON-NLS-1$
- );
+ return getProperty("os.name"); //$NON-NLS-1$
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java
index cedb159827..ccc19691b1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFInputStream.java
@@ -57,14 +57,12 @@ public class AutoCRLFInputStream extends InputStream {
this.detectBinary = detectBinary;
}
- /** {@inheritDoc} */
@Override
public int read() throws IOException {
final int read = read(single, 0, 1);
return read == 1 ? single[0] & 0xff : -1;
}
- /** {@inheritDoc} */
@Override
public int read(byte[] bs, int off, int len) throws IOException {
if (len == 0)
@@ -103,7 +101,6 @@ public class AutoCRLFInputStream extends InputStream {
return n;
}
- /** {@inheritDoc} */
@Override
public void close() throws IOException {
in.close();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java
index 305ccbd7e6..9fb316f28d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoCRLFOutputStream.java
@@ -65,14 +65,12 @@ public class AutoCRLFOutputStream extends OutputStream {
this.detectBinary = detectBinary;
}
- /** {@inheritDoc} */
@Override
public void write(int b) throws IOException {
onebytebuf[0] = (byte) b;
write(onebytebuf, 0, 1);
}
- /** {@inheritDoc} */
@Override
public void write(byte[] b) throws IOException {
int overflow = buffer(b, 0, b.length);
@@ -80,7 +78,6 @@ public class AutoCRLFOutputStream extends OutputStream {
write(b, b.length - overflow, overflow);
}
- /** {@inheritDoc} */
@Override
public void write(byte[] b, int startOff, int startLen)
throws IOException {
@@ -151,7 +148,6 @@ public class AutoCRLFOutputStream extends OutputStream {
write(binbuf, 0, cachedLen);
}
- /** {@inheritDoc} */
@Override
public void flush() throws IOException {
if (binbufcnt <= binbuf.length) {
@@ -161,7 +157,6 @@ public class AutoCRLFOutputStream extends OutputStream {
out.flush();
}
- /** {@inheritDoc} */
@Override
public void close() throws IOException {
flush();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java
index 7db882c074..4b9706a3ce 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFInputStream.java
@@ -147,52 +147,12 @@ public class AutoLFInputStream extends InputStream {
&& flags.contains(StreamFlag.FOR_CHECKOUT);
}
- /**
- * Creates a new InputStream, wrapping the specified stream.
- *
- * @param in
- * raw input stream
- * @param detectBinary
- * whether binaries should be detected
- * @since 2.0
- * @deprecated since 5.9, use {@link #create(InputStream, StreamFlag...)}
- * instead
- */
- @Deprecated
- public AutoLFInputStream(InputStream in, boolean detectBinary) {
- this(in, detectBinary, false);
- }
-
- /**
- * Creates a new InputStream, wrapping the specified stream.
- *
- * @param in
- * raw input stream
- * @param detectBinary
- * whether binaries should be detected
- * @param abortIfBinary
- * throw an IOException if the file is binary
- * @since 3.3
- * @deprecated since 5.9, use {@link #create(InputStream, StreamFlag...)}
- * instead
- */
- @Deprecated
- public AutoLFInputStream(InputStream in, boolean detectBinary,
- boolean abortIfBinary) {
- this.in = in;
- this.detectBinary = detectBinary;
- this.abortIfBinary = abortIfBinary;
- this.forCheckout = false;
- }
-
- /** {@inheritDoc} */
@Override
public int read() throws IOException {
final int read = read(single, 0, 1);
return read == 1 ? single[0] & 0xff : -1;
}
- /** {@inheritDoc} */
@Override
public int read(byte[] bs, int off, int len)
throws IOException {
@@ -242,7 +202,6 @@ public class AutoLFInputStream extends InputStream {
return isBinary;
}
- /** {@inheritDoc} */
@Override
public void close() throws IOException {
in.close();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java
index a0e9fb68c5..e56991d43a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/AutoLFOutputStream.java
@@ -69,14 +69,12 @@ public class AutoLFOutputStream extends OutputStream {
this.detectBinary = detectBinary;
}
- /** {@inheritDoc} */
@Override
public void write(int b) throws IOException {
onebytebuf[0] = (byte) b;
write(onebytebuf, 0, 1);
}
- /** {@inheritDoc} */
@Override
public void write(byte[] b) throws IOException {
int overflow = buffer(b, 0, b.length);
@@ -85,7 +83,6 @@ public class AutoLFOutputStream extends OutputStream {
}
}
- /** {@inheritDoc} */
@Override
public void write(byte[] b, int startOff, int startLen)
throws IOException {
@@ -164,7 +161,6 @@ public class AutoLFOutputStream extends OutputStream {
write(binbuf, 0, cachedLen);
}
- /** {@inheritDoc} */
@Override
public void flush() throws IOException {
if (binbufcnt <= binbuf.length) {
@@ -173,7 +169,6 @@ public class AutoLFOutputStream extends OutputStream {
out.flush();
}
- /** {@inheritDoc} */
@Override
public void close() throws IOException {
flush();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ByteBufferInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ByteBufferInputStream.java
new file mode 100644
index 0000000000..804f7f860a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ByteBufferInputStream.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2023, SAP SE and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.util.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.InvalidMarkException;
+import java.util.Objects;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.internal.JGitText;
+
+/**
+ * An {@link InputStream} backed by a {@link ByteBuffer}.
+ *
+ * @since 6.8
+ */
+public class ByteBufferInputStream extends InputStream {
+
+ private ByteBuffer buf;
+
+ /**
+ * Creates a {@link ByteBufferInputStream}
+ *
+ * @param buf
+ * the ByteBuffer backing the stream
+ */
+ public ByteBufferInputStream(@NonNull ByteBuffer buf) {
+ this.buf = buf;
+ }
+
+ @Override
+ public int read() throws IOException {
+ nullCheck();
+ if (buf.hasRemaining()) {
+ return buf.get() & 0xFF;
+ }
+ return -1;
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ nullCheck();
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ nullCheck();
+ Objects.checkFromIndexSize(off, len, b.length);
+ if (len == 0) {
+ return 0;
+ }
+ int length = Math.min(buf.remaining(), len);
+ if (length == 0) {
+ return -1;
+ }
+ buf.get(b, off, length);
+ return length;
+ }
+
+ @Override
+ public byte[] readAllBytes() throws IOException {
+ return readNBytes(buf.remaining());
+ }
+
+ @Override
+ public byte[] readNBytes(int len) throws IOException {
+ int l = Math.min(len, buf.remaining());
+ byte[] b = new byte[l];
+ read(b);
+ return b;
+ }
+
+ @Override
+ public int readNBytes(byte[] b, int off, int len) throws IOException {
+ return read(b, off, len);
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ nullCheck();
+ if (n <= 0) {
+ return 0;
+ }
+ // ByteBuffer index has type int
+ int delta = n > Integer.MAX_VALUE ? buf.remaining()
+ : Math.min((int) n, buf.remaining());
+ buf.position(buf.position() + delta);
+ return delta;
+ }
+
+ @Override
+ public int available() throws IOException {
+ nullCheck();
+ return buf.remaining();
+ }
+
+ @Override
+ public void close() {
+ buf = null;
+ }
+
+ @Override
+ public synchronized void mark(int readlimit) {
+ buf.mark();
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ try {
+ buf.reset();
+ } catch (InvalidMarkException e) {
+ throw new IOException(e);
+ }
+ }
+
+ @Override
+ public boolean markSupported() {
+ return true;
+ }
+
+ private void nullCheck() throws IOException {
+ if (buf == null) {
+ throw new IOException(JGitText.get().inputStreamClosed);
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/CountingOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/CountingOutputStream.java
index 782f3f4ca6..d0049d29de 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/CountingOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/CountingOutputStream.java
@@ -39,27 +39,23 @@ public class CountingOutputStream extends OutputStream {
return cnt;
}
- /** {@inheritDoc} */
@Override
public void write(int val) throws IOException {
out.write(val);
cnt++;
}
- /** {@inheritDoc} */
@Override
public void write(byte[] buf, int off, int len) throws IOException {
out.write(buf, off, len);
cnt += len;
}
- /** {@inheritDoc} */
@Override
public void flush() throws IOException {
out.flush();
}
- /** {@inheritDoc} */
@Override
public void close() throws IOException {
out.close();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/DisabledOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/DisabledOutputStream.java
index f1bfbe29a5..03c25bb702 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/DisabledOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/DisabledOutputStream.java
@@ -27,7 +27,6 @@ public final class DisabledOutputStream extends OutputStream {
// more than one instance from being created.
}
- /** {@inheritDoc} */
@Override
public void write(int b) throws IOException {
// We shouldn't be writing output at this stage, there
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
index 7e46afbc4b..888b8fbb09 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/InterruptTimer.java
@@ -104,10 +104,20 @@ public final class InterruptTimer {
*/
public void terminate() {
state.terminate();
+ boolean interrupted = false;
try {
- thread.join();
- } catch (InterruptedException e) {
- //
+ while (true) {
+ try {
+ thread.join();
+ return;
+ } catch (InterruptedException e) {
+ interrupted = true;
+ }
+ }
+ } finally {
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java
index 2bbdbefd38..1faf6ea9aa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/IsolatedOutputStream.java
@@ -58,13 +58,11 @@ public class IsolatedOutputStream extends OutputStream {
new ArrayBlockingQueue<>(1), new NamedThreadFactory());
}
- /** {@inheritDoc} */
@Override
public void write(int ch) throws IOException {
write(new byte[] { (byte) ch }, 0, 1);
}
- /** {@inheritDoc} */
@Override
public void write(byte[] buf, int pos, int cnt)
throws IOException {
@@ -75,7 +73,6 @@ public class IsolatedOutputStream extends OutputStream {
});
}
- /** {@inheritDoc} */
@Override
public void flush() throws IOException {
checkClosed();
@@ -85,7 +82,6 @@ public class IsolatedOutputStream extends OutputStream {
});
}
- /** {@inheritDoc} */
@Override
public void close() throws IOException {
if (!copier.isShutdown()) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/LimitedInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/LimitedInputStream.java
index 88006242d9..681a52988e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/LimitedInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/LimitedInputStream.java
@@ -47,21 +47,18 @@ public abstract class LimitedInputStream extends FilterInputStream {
this.limit = limit;
}
- /** {@inheritDoc} */
@Override
public int available() throws IOException {
return (int) Math.min(in.available(), left);
}
// it's okay to mark even if mark isn't supported, as reset won't work
- /** {@inheritDoc} */
@Override
public synchronized void mark(int readLimit) {
in.mark(readLimit);
mark = left;
}
- /** {@inheritDoc} */
@Override
public int read() throws IOException {
if (left == 0) {
@@ -78,7 +75,6 @@ public abstract class LimitedInputStream extends FilterInputStream {
return result;
}
- /** {@inheritDoc} */
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (left == 0) {
@@ -96,7 +92,6 @@ public abstract class LimitedInputStream extends FilterInputStream {
return result;
}
- /** {@inheritDoc} */
@Override
public synchronized void reset() throws IOException {
if (!in.markSupported())
@@ -109,7 +104,6 @@ public abstract class LimitedInputStream extends FilterInputStream {
left = mark;
}
- /** {@inheritDoc} */
@Override
public long skip(long n) throws IOException {
n = Math.min(n, left);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java
index 2637766153..8d5b8fdcba 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/MessageWriter.java
@@ -51,7 +51,6 @@ public class MessageWriter extends Writer {
enc = new OutputStreamWriter(getRawStream(), UTF_8);
}
- /** {@inheritDoc} */
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
synchronized (buf) {
@@ -71,20 +70,17 @@ public class MessageWriter extends Writer {
return buf;
}
- /** {@inheritDoc} */
@Override
public void close() throws IOException {
// Do nothing, we are buffered with no resources.
}
- /** {@inheritDoc} */
@Override
public void flush() throws IOException {
// Do nothing, we are buffered with no resources.
}
/** @return string version of all buffered data. */
- /** {@inheritDoc} */
@Override
public String toString() {
return RawParseUtils.decode(buf.toByteArray());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/SilentInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/SilentInputStream.java
new file mode 100644
index 0000000000..8c2c61a434
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/SilentInputStream.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2025 Thomas Wolf <twolf@apache.org> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.util.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An {@link InputStream} that swallows exceptions on {@link #close()}.
+ *
+ * @since 7.4
+ */
+public class SilentInputStream extends FilterInputStream {
+
+ private static final Logger LOG = LoggerFactory
+ .getLogger(SilentInputStream.class);
+
+ /**
+ * Wraps an existing {@link InputStream}.
+ *
+ * @param in
+ * {@link InputStream} to wrap
+ */
+ public SilentInputStream(InputStream in) {
+ super(in);
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ super.close();
+ } catch (IOException e) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Exception ignored while closing input stream", e); //$NON-NLS-1$
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java
index c0724e43f2..ed412fa6f5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java
@@ -67,7 +67,6 @@ public class StreamCopyThread extends Thread {
}
}
- /** {@inheritDoc} */
@Override
public void run() {
try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java
index 56d0169f7d..96376bfc6a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeInputStream.java
@@ -46,7 +46,6 @@ public class TeeInputStream extends InputStream {
this.dst = dst;
}
- /** {@inheritDoc} */
@Override
public int read() throws IOException {
byte[] b = skipBuffer();
@@ -54,7 +53,6 @@ public class TeeInputStream extends InputStream {
return n == 1 ? b[0] & 0xff : -1;
}
- /** {@inheritDoc} */
@Override
public long skip(long count) throws IOException {
long skipped = 0;
@@ -71,7 +69,6 @@ public class TeeInputStream extends InputStream {
return skipped;
}
- /** {@inheritDoc} */
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (len == 0)
@@ -83,7 +80,6 @@ public class TeeInputStream extends InputStream {
return n;
}
- /** {@inheritDoc} */
@Override
public void close() throws IOException {
byte[] b = skipBuffer();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeOutputStream.java
index e6fdd709b2..ab084a66f2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeOutputStream.java
@@ -34,35 +34,30 @@ public class TeeOutputStream extends OutputStream {
this.stream2 = stream2;
}
- /** {@inheritDoc} */
@Override
public void write(byte[] buf) throws IOException {
this.stream1.write(buf);
this.stream2.write(buf);
}
- /** {@inheritDoc} */
@Override
public void write(byte[] buf, int off, int len) throws IOException {
this.stream1.write(buf, off, len);
this.stream2.write(buf, off, len);
}
- /** {@inheritDoc} */
@Override
public void write(int b) throws IOException {
this.stream1.write(b);
this.stream2.write(b);
}
- /** {@inheritDoc} */
@Override
public void flush() throws IOException {
this.stream1.flush();
this.stream2.flush();
}
- /** {@inheritDoc} */
@Override
public void close() throws IOException {
try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java
index 3bc92d5bcd..13982b133c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/ThrowingPrintWriter.java
@@ -11,8 +11,6 @@ package org.eclipse.jgit.util.io;
import java.io.IOException;
import java.io.Writer;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
import org.eclipse.jgit.util.SystemReader;
@@ -35,25 +33,19 @@ public class ThrowingPrintWriter extends Writer {
*/
public ThrowingPrintWriter(Writer out) {
this.out = out;
- LF = AccessController
- .doPrivileged((PrivilegedAction<String>) () -> SystemReader
- .getInstance().getProperty("line.separator") //$NON-NLS-1$
- );
+ LF = SystemReader.getInstance().getProperty("line.separator"); //$NON-NLS-1$
}
- /** {@inheritDoc} */
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
out.write(cbuf, off, len);
}
- /** {@inheritDoc} */
@Override
public void flush() throws IOException {
out.flush();
}
- /** {@inheritDoc} */
@Override
public void close() throws IOException {
out.close();
@@ -62,8 +54,10 @@ public class ThrowingPrintWriter extends Writer {
/**
* Print a string and terminate with a line feed.
*
- * @param s a {@link java.lang.String} object.
+ * @param s
+ * a {@link java.lang.String} object.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void println(String s) throws IOException {
print(s + LF);
@@ -73,6 +67,7 @@ public class ThrowingPrintWriter extends Writer {
* Print a platform dependent new line
*
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void println() throws IOException {
print(LF);
@@ -81,8 +76,10 @@ public class ThrowingPrintWriter extends Writer {
/**
* Print a char
*
- * @param value a char.
+ * @param value
+ * a char.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void print(char value) throws IOException {
print(String.valueOf(value));
@@ -94,6 +91,7 @@ public class ThrowingPrintWriter extends Writer {
* @param value
* an int.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void print(int value) throws IOException {
print(String.valueOf(value));
@@ -102,8 +100,10 @@ public class ThrowingPrintWriter extends Writer {
/**
* Print a long as string
*
- * @param value a long.
+ * @param value
+ * a long.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void print(long value) throws IOException {
print(String.valueOf(value));
@@ -112,8 +112,10 @@ public class ThrowingPrintWriter extends Writer {
/**
* Print a short as string
*
- * @param value a short.
+ * @param value
+ * a short.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void print(short value) throws IOException {
print(String.valueOf(value));
@@ -128,6 +130,7 @@ public class ThrowingPrintWriter extends Writer {
* @param args
* objects.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void format(String fmt, Object... args) throws IOException {
print(String.format(fmt, args));
@@ -139,6 +142,7 @@ public class ThrowingPrintWriter extends Writer {
* @param any
* an object.
* @throws java.io.IOException
+ * if an IO error occurred
*/
public void print(Object any) throws IOException {
out.write(String.valueOf(any));
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
index 1947b3bf04..4d9f83d233 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutInputStream.java
@@ -63,7 +63,6 @@ public class TimeoutInputStream extends FilterInputStream {
timeout = millis;
}
- /** {@inheritDoc} */
@Override
public int read() throws IOException {
try {
@@ -76,13 +75,11 @@ public class TimeoutInputStream extends FilterInputStream {
}
}
- /** {@inheritDoc} */
@Override
public int read(byte[] buf) throws IOException {
return read(buf, 0, buf.length);
}
- /** {@inheritDoc} */
@Override
public int read(byte[] buf, int off, int cnt) throws IOException {
try {
@@ -95,7 +92,6 @@ public class TimeoutInputStream extends FilterInputStream {
}
}
- /** {@inheritDoc} */
@Override
public long skip(long cnt) throws IOException {
try {
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
index 3fbf6ffdcb..afd798a1a2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TimeoutOutputStream.java
@@ -65,7 +65,6 @@ public class TimeoutOutputStream extends OutputStream {
timeout = millis;
}
- /** {@inheritDoc} */
@Override
public void write(int b) throws IOException {
try {
@@ -78,13 +77,11 @@ public class TimeoutOutputStream extends OutputStream {
}
}
- /** {@inheritDoc} */
@Override
public void write(byte[] buf) throws IOException {
write(buf, 0, buf.length);
}
- /** {@inheritDoc} */
@Override
public void write(byte[] buf, int off, int len) throws IOException {
try {
@@ -97,7 +94,6 @@ public class TimeoutOutputStream extends OutputStream {
}
}
- /** {@inheritDoc} */
@Override
public void flush() throws IOException {
try {
@@ -110,7 +106,6 @@ public class TimeoutOutputStream extends OutputStream {
}
}
- /** {@inheritDoc} */
@Override
public void close() throws IOException {
try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java
index 459888190f..7e950f6529 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/UnionInputStream.java
@@ -12,8 +12,8 @@ package org.eclipse.jgit.util.io;
import java.io.IOException;
import java.io.InputStream;
-import java.util.Iterator;
-import java.util.LinkedList;
+import java.util.ArrayDeque;
+import java.util.Deque;
/**
* An InputStream which reads from one or more InputStreams.
@@ -34,7 +34,7 @@ public class UnionInputStream extends InputStream {
}
};
- private final LinkedList<InputStream> streams = new LinkedList<>();
+ private final Deque<InputStream> streams = new ArrayDeque<>();
/**
* Create an empty InputStream that is currently at EOF state.
@@ -91,7 +91,6 @@ public class UnionInputStream extends InputStream {
return streams.isEmpty();
}
- /** {@inheritDoc} */
@Override
public int read() throws IOException {
for (;;) {
@@ -106,7 +105,6 @@ public class UnionInputStream extends InputStream {
}
}
- /** {@inheritDoc} */
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (len == 0)
@@ -123,13 +121,11 @@ public class UnionInputStream extends InputStream {
}
}
- /** {@inheritDoc} */
@Override
public int available() throws IOException {
return head().available();
}
- /** {@inheritDoc} */
@Override
public long skip(long count) throws IOException {
long skipped = 0;
@@ -163,19 +159,18 @@ public class UnionInputStream extends InputStream {
return skipped;
}
- /** {@inheritDoc} */
@Override
public void close() throws IOException {
IOException err = null;
- for (Iterator<InputStream> i = streams.iterator(); i.hasNext();) {
+ for (InputStream stream : streams) {
try {
- i.next().close();
+ stream.close();
} catch (IOException closeError) {
err = closeError;
}
- i.remove();
}
+ streams.clear();
if (err != null)
throw err;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java
index 56e90d0636..0a56c83040 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java
@@ -193,7 +193,6 @@ public abstract class SHA1 {
* <p>
* Implementations not supporting collision detection always return
* {@code false}.
- * <p>
*
* @return {@code true} if a likely collision was detected.
*/
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1Java.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1Java.java
index 213ee97531..33e6875883 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1Java.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1Java.java
@@ -407,7 +407,7 @@ class SHA1Java extends SHA1 {
private static int s1(int a, int b, int c, int d, int w_t) {
return rotateLeft(a, 5)
// f: 0 <= t <= 19
- + ((b & c) | ((~b) & d))
+ + ((b & c) | (~b & d))
+ 0x5A827999 + w_t;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/UbcCheck.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/UbcCheck.java
index cebdbee27a..91ee3cc9be 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/UbcCheck.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/UbcCheck.java
@@ -89,6 +89,7 @@ final class UbcCheck {
private static final int DV_II_55_0_bit = 1 << 30;
private static final int DV_II_56_0_bit = 1 << 31;
+ @SuppressWarnings("UnnecessaryParentheses")
static int check(int[] w) {
int mask = ~0;
mask &= (((((w[44] ^ w[45]) >>> 29) & 1) - 1) | ~(DV_I_48_0_bit
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicSystemClock.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicSystemClock.java
index 66857b5bf8..4e079f08b5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicSystemClock.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/time/MonotonicSystemClock.java
@@ -37,7 +37,6 @@ public class MonotonicSystemClock implements MonotonicClock {
}
}
- /** {@inheritDoc} */
@Override
public ProposedTimestamp propose() {
final long u = nowMicros();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java
index 8c20423bc6..a20eaaf908 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/time/ProposedTimestamp.java
@@ -138,7 +138,10 @@ public abstract class ProposedTimestamp implements AutoCloseable {
* Get time since epoch, with up to microsecond resolution.
*
* @return time since epoch, with up to microsecond resolution.
+ *
+ * @deprecated Use instant() instead
*/
+ @Deprecated(since = "7.2")
public Timestamp timestamp() {
return Timestamp.from(instant());
}
@@ -147,7 +150,10 @@ public abstract class ProposedTimestamp implements AutoCloseable {
* Get time since epoch, with up to millisecond resolution.
*
* @return time since epoch, with up to millisecond resolution.
+ *
+ * @deprecated Use instant() instead
*/
+ @Deprecated(since = "7.2")
public Date date() {
return new Date(millis());
}
@@ -162,7 +168,6 @@ public abstract class ProposedTimestamp implements AutoCloseable {
// Do nothing by default.
}
- /** {@inheritDoc} */
@Override
public String toString() {
return instant().toString();