aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit/src
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit/src')
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java73
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java29
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java100
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java54
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java30
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java38
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java37
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/errors/AbortedByHookException.java28
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java117
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/blame/Candidate.java64
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java2
-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.java33
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java20
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java86
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/NoMergeBaseException.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleException.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java21
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/hooks/CommitMsgHook.java21
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java60
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java95
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java21
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/hooks/PreCommitHook.java21
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java21
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java37
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java36
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java55
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchSystem.java21
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java28
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java28
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java22
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java185
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableBatchRefUpdate.java196
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableDatabase.java243
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableStack.java (renamed from org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableStack.java)18
-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.java669
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java766
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java254
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java26
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GcLog.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java188
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackedBatchRefUpdate.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java24
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryUpdate.java3
-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/ReflogWriter.java34
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java102
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/CachedPackUriProvider.java21
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackExt.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java26
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockReader.java19
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/BlockWriter.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/LogCursor.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/MergedReftable.java84
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/Reftable.java38
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableBatchRefUpdate.java (renamed from org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableBatchRefUpdate.java)337
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java119
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableDatabase.java350
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java44
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReflogReader.java125
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableWriter.java42
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/http/NetscapeCookieFileCache.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/transport/ssh/OpenSshConfigFile.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java19
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java33
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java80
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java198
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/InflaterCache.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java2
-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.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java28
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilderFactory.java27
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java137
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeConfig.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java94
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java33
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java38
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteParser.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/patch/BinaryHunk.java2
-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.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java2
-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.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmapCalculator.java136
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmappedReachabilityChecker.java120
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PedestrianReachabilityChecker.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ReachabilityChecker.java37
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java22
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackStatistics.java30
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java68
-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.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java1971
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProviderUserInfo.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java1911
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java16
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java2
-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.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportProtocol.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java318
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/resolver/FileResolver.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java70
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/IndexDiffFilter.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathSuffixFilter.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java159
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS_POSIX.java22
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java24
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java27
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java31
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java20
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/Monitoring.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java50
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/LimitedInputStream.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/sha1/SHA1.java2
193 files changed, 7317 insertions, 4216 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java
index f7576e9e9b..a69aa70c6e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011, GitHub Inc.
+ * Copyright (C) 2011, 2019 GitHub Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -42,11 +42,7 @@
*/
package org.eclipse.jgit.api;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -56,17 +52,10 @@ import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.blame.BlameGenerator;
import org.eclipse.jgit.blame.BlameResult;
import org.eclipse.jgit.diff.DiffAlgorithm;
-import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.diff.RawTextComparator;
-import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.treewalk.WorkingTreeOptions;
-import org.eclipse.jgit.util.IO;
-import org.eclipse.jgit.util.io.AutoLFInputStream;
/**
* Blame command for building a {@link org.eclipse.jgit.blame.BlameResult} for a
@@ -221,69 +210,11 @@ public class BlameCommand extends GitCommand<BlameResult> {
else if (startCommit != null)
gen.push(null, startCommit);
else {
- gen.push(null, repo.resolve(Constants.HEAD));
- if (!repo.isBare()) {
- DirCache dc = repo.readDirCache();
- int entry = dc.findEntry(path);
- if (0 <= entry)
- gen.push(null, dc.getEntry(entry).getObjectId());
-
- File inTree = new File(repo.getWorkTree(), path);
- if (repo.getFS().isFile(inTree)) {
- RawText rawText = getRawText(inTree);
- gen.push(null, rawText);
- }
- }
+ gen.prepareHead();
}
return gen.computeBlameResult();
} catch (IOException e) {
throw new JGitInternalException(e.getMessage(), e);
}
}
-
- private RawText getRawText(File inTree) throws IOException,
- FileNotFoundException {
- RawText rawText;
-
- WorkingTreeOptions workingTreeOptions = getRepository().getConfig()
- .get(WorkingTreeOptions.KEY);
- AutoCRLF autoCRLF = workingTreeOptions.getAutoCRLF();
- switch (autoCRLF) {
- case FALSE:
- case INPUT:
- // Git used the repo format on checkout, but other tools
- // may change the format to CRLF. We ignore that here.
- rawText = new RawText(inTree);
- break;
- case TRUE:
- try (AutoLFInputStream in = new AutoLFInputStream(
- new FileInputStream(inTree), true)) {
- // Canonicalization should lead to same or shorter length
- // (CRLF to LF), so the file size on disk is an upper size bound
- rawText = new RawText(toByteArray(in, (int) inTree.length()));
- }
- break;
- default:
- throw new IllegalArgumentException(
- "Unknown autocrlf option " + autoCRLF); //$NON-NLS-1$
- }
- return rawText;
- }
-
- private static byte[] toByteArray(InputStream source, int upperSizeLimit)
- throws IOException {
- byte[] buffer = new byte[upperSizeLimit];
- try {
- int read = IO.readFully(source, buffer, 0);
- if (read == upperSizeLimit)
- return buffer;
- else {
- byte[] copy = new byte[read];
- System.arraycopy(buffer, 0, copy, 0, read);
- return copy;
- }
- } finally {
- source.close();
- }
- }
}
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 e05f6f1bd6..6d157bd0a1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -137,7 +137,7 @@ public class CheckoutCommand extends GitCommand<Ref> {
/**
* Stage to check out, see {@link CheckoutCommand#setStage(Stage)}.
*/
- public static enum Stage {
+ public enum Stage {
/**
* Base stage (#1)
*/
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 c9dd547b49..aa63725c55 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java
@@ -57,6 +57,7 @@ import org.eclipse.jgit.api.errors.UnmergedPathsException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
@@ -129,9 +130,10 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> {
// get the head commit
Ref headRef = repo.exactRef(Constants.HEAD);
- if (headRef == null)
+ if (headRef == null) {
throw new NoHeadException(
JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported);
+ }
newHead = revWalk.parseCommit(headRef.getObjectId());
@@ -140,8 +142,9 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> {
// get the commit to be cherry-picked
// handle annotated tags
ObjectId srcObjectId = src.getPeeledObjectId();
- if (srcObjectId == null)
+ if (srcObjectId == null) {
srcObjectId = src.getObjectId();
+ }
RevCommit srcCommit = revWalk.parseCommit(srcObjectId);
// get the parent of the commit to cherry-pick
@@ -157,26 +160,33 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> {
merger.setCommitNames(new String[] { "BASE", ourName, //$NON-NLS-1$
cherryPickName });
if (merger.merge(newHead, srcCommit)) {
+ if (!merger.getModifiedFiles().isEmpty()) {
+ repo.fireEvent(new WorkingTreeModifiedEvent(
+ merger.getModifiedFiles(), null));
+ }
if (AnyObjectId.isEqual(newHead.getTree().getId(),
- merger.getResultTreeId()))
+ merger.getResultTreeId())) {
continue;
+ }
DirCacheCheckout dco = new DirCacheCheckout(repo,
newHead.getTree(), repo.lockDirCache(),
merger.getResultTreeId());
dco.setFailOnConflict(true);
dco.setProgressMonitor(monitor);
dco.checkout();
- if (!noCommit)
+ if (!noCommit) {
newHead = new Git(getRepository()).commit()
.setMessage(srcCommit.getFullMessage())
.setReflogComment(reflogPrefix + " " //$NON-NLS-1$
+ srcCommit.getShortMessage())
.setAuthor(srcCommit.getAuthorIdent())
.setNoVerify(true).call();
+ }
cherryPickedRefs.add(src);
} else {
- if (merger.failed())
+ if (merger.failed()) {
return new CherryPickResult(merger.getFailingPaths());
+ }
// there are merge conflicts
@@ -184,10 +194,14 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> {
.formatWithConflicts(srcCommit.getFullMessage(),
merger.getUnmergedPaths());
- if (!noCommit)
+ if (!noCommit) {
repo.writeCherryPickHead(srcCommit.getId());
+ }
repo.writeMergeCommitMsg(message);
+ repo.fireEvent(new WorkingTreeModifiedEvent(
+ merger.getModifiedFiles(), null));
+
return CherryPickResult.CONFLICT;
}
}
@@ -213,10 +227,11 @@ public class CherryPickCommand extends GitCommand<CherryPickResult> {
Integer.valueOf(srcCommit.getParentCount())));
srcParent = srcCommit.getParent(0);
} else {
- if (mainlineParentNumber.intValue() > srcCommit.getParentCount())
+ if (mainlineParentNumber.intValue() > srcCommit.getParentCount()) {
throw new JGitInternalException(MessageFormat.format(
JGitText.get().commitDoesNotHaveGivenParent, srcCommit,
mainlineParentNumber));
+ }
srcParent = srcCommit
.getParent(mainlineParentNumber.intValue() - 1);
}
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 9f63d0f005..7008cd49a8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -77,8 +77,8 @@ import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.TagOpt;
import org.eclipse.jgit.transport.URIish;
-import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FileUtils;
/**
* Clone a repository into a new working directory
@@ -106,6 +106,8 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
private boolean cloneAllBranches;
+ private boolean mirror;
+
private boolean cloneSubmodules;
private boolean noCheckout;
@@ -118,6 +120,12 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
private boolean gitDirExistsInitially;
+ private FETCH_TYPE fetchType;
+
+ private enum FETCH_TYPE {
+ MULTIPLE_BRANCHES, ALL_BRANCHES, MIRROR
+ }
+
/**
* Callback for status of clone operation.
*
@@ -191,6 +199,7 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
throw new InvalidRemoteException(
MessageFormat.format(JGitText.get().invalidURL, uri));
}
+ setFetchType();
@SuppressWarnings("resource") // Closed by caller
Repository repository = init();
FetchResult fetchResult = null;
@@ -234,6 +243,20 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
return new Git(repository, true);
}
+ private void setFetchType() {
+ if (mirror) {
+ fetchType = FETCH_TYPE.MIRROR;
+ setBare(true);
+ } else if (cloneAllBranches) {
+ fetchType = FETCH_TYPE.ALL_BRANCHES;
+ } else if (branchesToClone != null && !branchesToClone.isEmpty()) {
+ fetchType = FETCH_TYPE.MULTIPLE_BRANCHES;
+ } else {
+ // Default: neither mirror nor all nor specific refs given
+ fetchType = FETCH_TYPE.ALL_BRANCHES;
+ }
+ }
+
private static boolean isNonEmptyDirectory(File dir) {
if (dir != null && dir.exists()) {
File[] files = dir.listFiles();
@@ -282,12 +305,11 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
RemoteConfig config = new RemoteConfig(clonedRepo.getConfig(), remote);
config.addURI(u);
- final String dst = (bare ? Constants.R_HEADS : Constants.R_REMOTES
- + config.getName() + '/') + '*';
- boolean fetchAll = cloneAllBranches || branchesToClone == null
- || branchesToClone.isEmpty();
+ boolean fetchAll = fetchType == FETCH_TYPE.ALL_BRANCHES
+ || fetchType == FETCH_TYPE.MIRROR;
- config.setFetchRefSpecs(calculateRefSpecs(fetchAll, dst));
+ config.setFetchRefSpecs(calculateRefSpecs(fetchType, config.getName()));
+ config.setMirror(fetchType == FETCH_TYPE.MIRROR);
config.update(clonedRepo.getConfig());
clonedRepo.getConfig().save();
@@ -302,26 +324,33 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
return command.call();
}
- private List<RefSpec> calculateRefSpecs(boolean fetchAll, String dst) {
- RefSpec heads = new RefSpec();
- heads = heads.setForceUpdate(true);
- heads = heads.setSourceDestination(Constants.R_HEADS + '*', dst);
+ private List<RefSpec> calculateRefSpecs(FETCH_TYPE type,
+ String remoteName) {
List<RefSpec> specs = new ArrayList<>();
- if (!fetchAll) {
- RefSpec tags = new RefSpec();
- tags = tags.setForceUpdate(true);
- tags = tags.setSourceDestination(Constants.R_TAGS + '*',
- Constants.R_TAGS + '*');
- for (String selectedRef : branchesToClone) {
- if (heads.matchSource(selectedRef)) {
- specs.add(heads.expandFromSource(selectedRef));
- } else if (tags.matchSource(selectedRef)) {
- specs.add(tags.expandFromSource(selectedRef));
+ if (type == FETCH_TYPE.MIRROR) {
+ specs.add(new RefSpec().setForceUpdate(true).setSourceDestination(
+ Constants.R_REFS + '*', Constants.R_REFS + '*'));
+ } else {
+ RefSpec heads = new RefSpec();
+ heads = heads.setForceUpdate(true);
+ final String dst = (bare ? Constants.R_HEADS
+ : Constants.R_REMOTES + remoteName + '/') + '*';
+ heads = heads.setSourceDestination(Constants.R_HEADS + '*', dst);
+ if (type == FETCH_TYPE.MULTIPLE_BRANCHES) {
+ RefSpec tags = new RefSpec().setForceUpdate(true)
+ .setSourceDestination(Constants.R_TAGS + '*',
+ Constants.R_TAGS + '*');
+ for (String selectedRef : branchesToClone) {
+ if (heads.matchSource(selectedRef)) {
+ specs.add(heads.expandFromSource(selectedRef));
+ } else if (tags.matchSource(selectedRef)) {
+ specs.add(tags.expandFromSource(selectedRef));
+ }
}
+ } else {
+ // We'll fetch the tags anyway.
+ specs.add(heads);
}
- } else {
- // We'll fetch the tags anyway.
- specs.add(heads);
}
return specs;
}
@@ -614,6 +643,26 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
}
/**
+ * Set up a mirror of the source repository. This implies that a bare
+ * repository will be created. Compared to {@link #setBare},
+ * {@code #setMirror} not only maps local branches of the source to local
+ * branches of the target, it maps all refs (including remote-tracking
+ * branches, notes etc.) and sets up a refspec configuration such that all
+ * these refs are overwritten by a git remote update in the target
+ * repository.
+ *
+ * @param mirror
+ * whether to mirror all refs from the source repository
+ *
+ * @return {@code this}
+ * @since 5.6
+ */
+ public CloneCommand setMirror(boolean mirror) {
+ this.mirror = mirror;
+ return this;
+ }
+
+ /**
* Set whether to clone submodules
*
* @param cloneSubmodules
@@ -630,8 +679,9 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
* Set the branches or tags to clone.
* <p>
* This is ignored if {@link #setCloneAllBranches(boolean)
- * setCloneAllBranches(true)} is used. If {@code branchesToClone} is
- * {@code null} or empty, it's also ignored and all branches will be cloned.
+ * setCloneAllBranches(true)} or {@link #setMirror(boolean) setMirror(true)}
+ * is used. If {@code branchesToClone} is {@code null} or empty, it's also
+ * ignored.
* </p>
*
* @param branchesToClone
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 b55987ead4..b32f7ab20b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -143,6 +143,8 @@ public class CommitCommand extends GitCommand<RevCommit> {
private HashMap<String, PrintStream> hookOutRedirect = new HashMap<>(3);
+ private HashMap<String, PrintStream> hookErrRedirect = new HashMap<>(3);
+
private Boolean allowEmpty;
private Boolean signCommit;
@@ -188,7 +190,8 @@ public class CommitCommand extends GitCommand<RevCommit> {
state.name()));
if (!noVerify) {
- Hooks.preCommit(repo, hookOutRedirect.get(PreCommitHook.NAME))
+ Hooks.preCommit(repo, hookOutRedirect.get(PreCommitHook.NAME),
+ hookErrRedirect.get(PreCommitHook.NAME))
.call();
}
@@ -230,7 +233,8 @@ public class CommitCommand extends GitCommand<RevCommit> {
if (!noVerify) {
message = Hooks
.commitMsg(repo,
- hookOutRedirect.get(CommitMsgHook.NAME))
+ hookOutRedirect.get(CommitMsgHook.NAME),
+ hookErrRedirect.get(CommitMsgHook.NAME))
.setCommitMessage(message).call();
}
@@ -311,7 +315,8 @@ public class CommitCommand extends GitCommand<RevCommit> {
repo.writeRevertHead(null);
}
Hooks.postCommit(repo,
- hookOutRedirect.get(PostCommitHook.NAME)).call();
+ hookOutRedirect.get(PostCommitHook.NAME),
+ hookErrRedirect.get(PostCommitHook.NAME)).call();
return revCommit;
}
@@ -519,7 +524,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
int position = Collections.binarySearch(only, p);
if (position >= 0)
return position;
- int l = p.lastIndexOf("/"); //$NON-NLS-1$
+ int l = p.lastIndexOf('/');
if (l < 1)
break;
p = p.substring(0, l);
@@ -891,6 +896,23 @@ public class CommitCommand extends GitCommand<RevCommit> {
}
/**
+ * Set the error stream for all hook scripts executed by this command
+ * (pre-commit, commit-msg, post-commit). If not set it defaults to
+ * {@code System.err}.
+ *
+ * @param hookStdErr
+ * the error stream for hook scripts executed by this command
+ * @return {@code this}
+ * @since 5.6
+ */
+ public CommitCommand setHookErrorStream(PrintStream hookStdErr) {
+ setHookErrorStream(PreCommitHook.NAME, hookStdErr);
+ setHookErrorStream(CommitMsgHook.NAME, hookStdErr);
+ setHookErrorStream(PostCommitHook.NAME, hookStdErr);
+ return this;
+ }
+
+ /**
* Set the output stream for a selected hook script executed by this command
* (pre-commit, commit-msg, post-commit). If not set it defaults to
* {@code System.out}.
@@ -916,6 +938,30 @@ public class CommitCommand extends GitCommand<RevCommit> {
}
/**
+ * Set the error stream for a selected hook script executed by this command
+ * (pre-commit, commit-msg, post-commit). If not set it defaults to
+ * {@code System.err}.
+ *
+ * @param hookName
+ * name of the hook to set the output stream for
+ * @param hookStdErr
+ * the output stream to use for the selected hook
+ * @return {@code this}
+ * @since 5.6
+ */
+ public CommitCommand setHookErrorStream(String hookName,
+ PrintStream hookStdErr) {
+ if (!(PreCommitHook.NAME.equals(hookName)
+ || CommitMsgHook.NAME.equals(hookName)
+ || PostCommitHook.NAME.equals(hookName))) {
+ throw new IllegalArgumentException(MessageFormat
+ .format(JGitText.get().illegalHookName, hookName));
+ }
+ hookErrRedirect.put(hookName, hookStdErr);
+ return this;
+ }
+
+ /**
* Sets the signing key
* <p>
* Per spec of user.signingKey: this will be sent to the GPG program as is,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java
index f65b5735de..1c3c79041e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java
@@ -136,28 +136,32 @@ public class DiffCommand extends GitCommand<List<DiffEntry>> {
}
newTree = new DirCacheIterator(repo.readDirCache());
} else {
- if (oldTree == null)
+ if (oldTree == null) {
oldTree = new DirCacheIterator(repo.readDirCache());
- if (newTree == null)
+ }
+ if (newTree == null) {
newTree = new FileTreeIterator(repo);
+ }
}
diffFmt.setPathFilter(pathFilter);
List<DiffEntry> result = diffFmt.scan(oldTree, newTree);
- if (showNameAndStatusOnly)
- return result;
- else {
- if (contextLines >= 0)
- diffFmt.setContext(contextLines);
- if (destinationPrefix != null)
- diffFmt.setNewPrefix(destinationPrefix);
- if (sourcePrefix != null)
- diffFmt.setOldPrefix(sourcePrefix);
- diffFmt.format(result);
- diffFmt.flush();
+ if (showNameAndStatusOnly) {
return result;
}
+ if (contextLines >= 0) {
+ diffFmt.setContext(contextLines);
+ }
+ if (destinationPrefix != null) {
+ diffFmt.setNewPrefix(destinationPrefix);
+ }
+ if (sourcePrefix != null) {
+ diffFmt.setOldPrefix(sourcePrefix);
+ }
+ diffFmt.format(result);
+ diffFmt.flush();
+ return result;
} catch (IOException e) {
throw new JGitInternalException(e.getMessage(), e);
}
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 2c9c5f20cc..9020c58d46 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
@@ -360,17 +360,17 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> {
* @return whether to remove refs which no longer exist in the source
*/
public boolean isRemoveDeletedRefs() {
- if (removeDeletedRefs != null)
+ if (removeDeletedRefs != null) {
return removeDeletedRefs.booleanValue();
- else { // fall back to configuration
- boolean result = false;
- StoredConfig config = repo.getConfig();
- result = config.getBoolean(ConfigConstants.CONFIG_FETCH_SECTION,
- null, ConfigConstants.CONFIG_KEY_PRUNE, result);
- result = config.getBoolean(ConfigConstants.CONFIG_REMOTE_SECTION,
- remote, ConfigConstants.CONFIG_KEY_PRUNE, result);
- return result;
}
+ // fall back to configuration
+ boolean result = false;
+ StoredConfig config = repo.getConfig();
+ result = config.getBoolean(ConfigConstants.CONFIG_FETCH_SECTION, null,
+ ConfigConstants.CONFIG_KEY_PRUNE, result);
+ result = config.getBoolean(ConfigConstants.CONFIG_REMOTE_SECTION,
+ remote, ConfigConstants.CONFIG_KEY_PRUNE, result);
+ return result;
}
/**
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 7ea277157d..474e2f5736 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
@@ -243,9 +243,8 @@ public class GarbageCollectCommand extends GitCommand<Properties> {
if (repo instanceof FileRepository) {
GC gc = new GC((FileRepository) repo);
return toProperties(gc.getStatistics());
- } else {
- return new Properties();
}
+ return new Properties();
} catch (IOException e) {
throw new JGitInternalException(
JGitText.get().couldNotGetRepoStatistics, e);
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 66de8ae131..217785d39e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java
@@ -105,6 +105,7 @@ public class LogCommand extends GitCommand<Iterable<RevCommit>> {
private RevFilter revFilter;
private final List<PathFilter> pathFilters = new ArrayList<>();
+ private final List<TreeFilter> excludeTreeFilters = new ArrayList<>();
private int maxCount = -1;
@@ -133,9 +134,22 @@ public class LogCommand extends GitCommand<Iterable<RevCommit>> {
@Override
public Iterable<RevCommit> call() throws GitAPIException, NoHeadException {
checkCallable();
- if (!pathFilters.isEmpty())
- walk.setTreeFilter(AndTreeFilter.create(
- PathFilterGroup.create(pathFilters), TreeFilter.ANY_DIFF));
+ List<TreeFilter> filters = new ArrayList<>();
+ if (!pathFilters.isEmpty()) {
+ filters.add(AndTreeFilter.create(PathFilterGroup.create(pathFilters), TreeFilter.ANY_DIFF));
+ }
+ if (!excludeTreeFilters.isEmpty()) {
+ for (TreeFilter f : excludeTreeFilters) {
+ filters.add(AndTreeFilter.create(f, TreeFilter.ANY_DIFF));
+ }
+ }
+ if (!filters.isEmpty()) {
+ if (filters.size() == 1) {
+ filters.add(TreeFilter.ANY_DIFF);
+ }
+ walk.setTreeFilter(AndTreeFilter.create(filters));
+
+ }
if (skip > -1 && maxCount > -1)
walk.setRevFilter(AndRevFilter.create(SkipRevFilter.create(skip),
MaxCountRevFilter.create(maxCount)));
@@ -310,6 +324,24 @@ public class LogCommand extends GitCommand<Iterable<RevCommit>> {
}
/**
+ * Show all commits that are not within any of the specified paths. The path
+ * must either name a file or a directory exactly and use <code>/</code>
+ * (slash) as separator. Note that regular expressions or wildcards are not
+ * yet supported. If a path is both added and excluded from the search, then
+ * the exclusion wins.
+ *
+ * @param path
+ * a repository-relative path (with <code>/</code> as separator)
+ * @return {@code this}
+ * @since 5.6
+ */
+ public LogCommand excludePath(String path) {
+ checkCallable();
+ excludeTreeFilters.add(PathFilter.create(path).negate());
+ return this;
+ }
+
+ /**
* Skip the number of commits before starting to show the commit output.
*
* @param 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 f9a9baf919..9a843f63a0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
@@ -409,27 +409,24 @@ public class MergeCommand extends GitCommand<MergeResult> {
new ObjectId[] { headCommit.getId(),
srcCommit.getId() }, mergeStatus,
mergeStrategy, null, msg);
- } else {
- if (failingPaths != null) {
- repo.writeMergeCommitMsg(null);
- repo.writeMergeHeads(null);
- return new MergeResult(null, merger.getBaseCommitId(),
- new ObjectId[] {
- headCommit.getId(), srcCommit.getId() },
- MergeStatus.FAILED, mergeStrategy,
- lowLevelResults, failingPaths, null);
- } else {
- String mergeMessageWithConflicts = new MergeMessageFormatter()
- .formatWithConflicts(mergeMessage,
- unmergedPaths);
- repo.writeMergeCommitMsg(mergeMessageWithConflicts);
- return new MergeResult(null, merger.getBaseCommitId(),
- new ObjectId[] { headCommit.getId(),
- srcCommit.getId() },
- MergeStatus.CONFLICTING, mergeStrategy,
- lowLevelResults, null);
- }
}
+ if (failingPaths != null) {
+ repo.writeMergeCommitMsg(null);
+ repo.writeMergeHeads(null);
+ return new MergeResult(null, merger.getBaseCommitId(),
+ new ObjectId[] { headCommit.getId(),
+ srcCommit.getId() },
+ MergeStatus.FAILED, mergeStrategy, lowLevelResults,
+ failingPaths, null);
+ }
+ String mergeMessageWithConflicts = new MergeMessageFormatter()
+ .formatWithConflicts(mergeMessage, unmergedPaths);
+ repo.writeMergeCommitMsg(mergeMessageWithConflicts);
+ return new MergeResult(null, merger.getBaseCommitId(),
+ new ObjectId[] { headCommit.getId(),
+ srcCommit.getId() },
+ MergeStatus.CONFLICTING, mergeStrategy, lowLevelResults,
+ null);
}
} catch (org.eclipse.jgit.errors.CheckoutConflictException e) {
List<String> conflicts = (dco == null) ? Collections
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 bdb2d1bbc5..ea3e2d9f83 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java
@@ -90,7 +90,7 @@ import org.eclipse.jgit.transport.TagOpt;
*/
public class PullCommand extends TransportCommand<PullCommand, PullResult> {
- private final static String DOT = "."; //$NON-NLS-1$
+ private static final String DOT = "."; //$NON-NLS-1$
private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
@@ -315,23 +315,24 @@ public class PullCommand extends TransportCommand<PullCommand, PullResult> {
Ref r = null;
if (fetchRes != null) {
r = fetchRes.getAdvertisedRef(remoteBranchName);
- if (r == null)
+ if (r == null) {
r = fetchRes.getAdvertisedRef(Constants.R_HEADS
+ remoteBranchName);
+ }
}
if (r == null) {
throw new RefNotAdvertisedException(MessageFormat.format(
JGitText.get().couldNotGetAdvertisedRef, remote,
remoteBranchName));
- } else {
- commitToMerge = r.getObjectId();
}
+ commitToMerge = r.getObjectId();
} else {
try {
commitToMerge = repo.resolve(remoteBranchName);
- if (commitToMerge == null)
+ if (commitToMerge == null) {
throw new RefNotFoundException(MessageFormat.format(
JGitText.get().refNotResolved, remoteBranchName));
+ }
} catch (IOException e) {
throw new JGitInternalException(
JGitText.get().exceptionCaughtDuringExecutionOfPullCommand,
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 0dacd4dfbf..6eed0b3f64 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -509,10 +509,10 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
monitor.beginTask(MessageFormat.format(
JGitText.get().applyingCommit,
commitToPick.getShortMessage()), ProgressMonitor.UNKNOWN);
- if (preserveMerges)
+ if (preserveMerges) {
return cherryPickCommitPreservingMerges(commitToPick);
- else
- return cherryPickCommitFlattening(commitToPick);
+ }
+ return cherryPickCommitFlattening(commitToPick);
} finally {
monitor.endTask();
}
@@ -539,11 +539,11 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
.call();
switch (cherryPickResult.getStatus()) {
case FAILED:
- if (operation == Operation.BEGIN)
+ if (operation == Operation.BEGIN) {
return abort(RebaseResult
.failed(cherryPickResult.getFailingPaths()));
- else
- return stop(commitToPick, Status.STOPPED);
+ }
+ return stop(commitToPick, Status.STOPPED);
case CONFLICTING:
return stop(commitToPick, Status.STOPPED);
case OK:
@@ -599,11 +599,11 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
CherryPickResult cherryPickResult = pickCommand.call();
switch (cherryPickResult.getStatus()) {
case FAILED:
- if (operation == Operation.BEGIN)
+ if (operation == Operation.BEGIN) {
return abort(RebaseResult.failed(
cherryPickResult.getFailingPaths()));
- else
- return stop(commitToPick, Status.STOPPED);
+ }
+ return stop(commitToPick, Status.STOPPED);
case CONFLICTING:
return stop(commitToPick, Status.STOPPED);
case OK:
@@ -833,7 +833,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
sb.append("# This is a combination of ").append(count)
.append(" commits.\n");
// Add the previous message without header (i.e first line)
- sb.append(currSquashMessage.substring(currSquashMessage.indexOf("\n") + 1));
+ sb.append(currSquashMessage
+ .substring(currSquashMessage.indexOf('\n') + 1));
sb.append("\n");
if (isSquash) {
sb.append("# This is the ").append(count).append(ordinal)
@@ -871,7 +872,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
static int parseSquashFixupSequenceCount(String currSquashMessage) {
String regex = "This is a combination of (.*) commits"; //$NON-NLS-1$
String firstLine = currSquashMessage.substring(0,
- currSquashMessage.indexOf("\n")); //$NON-NLS-1$
+ currSquashMessage.indexOf('\n'));
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(firstLine);
if (!matcher.find())
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 d7c9ad5e04..3031a197a8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
@@ -334,10 +334,10 @@ public class ResetCommand extends GitCommand<Ref> {
}
private String getRefOrHEAD() {
- if (ref != null)
+ if (ref != null) {
return ref;
- else
- return Constants.HEAD;
+ }
+ return Constants.HEAD;
}
/**
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 ddd60b6fa2..aa0055fb53 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java
@@ -58,6 +58,7 @@ import org.eclipse.jgit.api.errors.NoMessageException;
import org.eclipse.jgit.api.errors.UnmergedPathsException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.dircache.DirCacheCheckout;
+import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
@@ -175,6 +176,10 @@ public class RevertCommand extends GitCommand<RevCommit> {
+ "This reverts commit " + srcCommit.getId().getName() //$NON-NLS-1$
+ ".\n"; //$NON-NLS-1$
if (merger.merge(headCommit, srcParent)) {
+ if (!merger.getModifiedFiles().isEmpty()) {
+ repo.fireEvent(new WorkingTreeModifiedEvent(
+ merger.getModifiedFiles(), null));
+ }
if (AnyObjectId.isEqual(headCommit.getTree().getId(),
merger.getResultTreeId()))
continue;
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 52393695d9..74def9e897 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java
@@ -106,10 +106,10 @@ public class SubmoduleSyncCommand extends GitCommand<Map<String, String>> {
*/
protected String getHeadBranch(Repository subRepo) throws IOException {
Ref head = subRepo.exactRef(Constants.HEAD);
- if (head != null && head.isSymbolic())
+ if (head != null && head.isSymbolic()) {
return Repository.shortenRefName(head.getLeaf().getName());
- else
- return null;
+ }
+ return null;
}
/** {@inheritDoc} */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/AbortedByHookException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/AbortedByHookException.java
index db6440b55f..30a2d622a7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/AbortedByHookException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/AbortedByHookException.java
@@ -67,19 +67,26 @@ public class AbortedByHookException extends GitAPIException {
private final int returnCode;
/**
+ * The stderr output of the hook.
+ */
+ private final String hookStdErr;
+
+ /**
* Constructor for AbortedByHookException
*
- * @param message
- * The error details.
+ * @param hookStdErr
+ * The error details from the stderr output of the hook
* @param hookName
* The name of the hook that interrupted the command, must not be
* null.
* @param returnCode
* The return code of the hook process that has been run.
*/
- public AbortedByHookException(String message, String hookName,
+ public AbortedByHookException(String hookStdErr, String hookName,
int returnCode) {
- super(message);
+ super(MessageFormat.format(JGitText.get().commandRejectedByHook,
+ hookName, hookStdErr));
+ this.hookStdErr = hookStdErr;
this.hookName = hookName;
this.returnCode = returnCode;
}
@@ -102,10 +109,13 @@ public class AbortedByHookException extends GitAPIException {
return returnCode;
}
- /** {@inheritDoc} */
- @Override
- public String getMessage() {
- return MessageFormat.format(JGitText.get().commandRejectedByHook,
- hookName, super.getMessage());
+ /**
+ * Get the stderr output of the hook.
+ *
+ * @return A string containing the complete stderr output of the hook.
+ * @since 5.6
+ */
+ public String getHookStdErr() {
+ return hookStdErr;
}
}
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 f1df0da453..b732050bbb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
@@ -67,7 +67,7 @@ public final class Attribute {
* The attribute value state
* see also https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
*/
- public static enum State {
+ public enum State {
/** the attribute is set */
SET,
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 a91d8c282f..ead99c79c5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
@@ -91,7 +91,7 @@ public class AttributesRule {
continue;
}
- final int equalsIndex = attribute.indexOf("="); //$NON-NLS-1$
+ final int equalsIndex = attribute.indexOf('=');
if (equalsIndex == -1)
result.add(new Attribute(attribute, State.SET));
else {
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 9cec645679..d0aa292df4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameGenerator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011, Google Inc.
+ * Copyright (C) 2011, 2019 Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -48,11 +48,17 @@ import static org.eclipse.jgit.lib.FileMode.TYPE_FILE;
import static org.eclipse.jgit.lib.FileMode.TYPE_MASK;
import java.io.IOException;
+import java.io.InputStream;
+import java.text.MessageFormat;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.List;
import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.api.errors.NoHeadException;
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.diff.DiffAlgorithm;
@@ -63,8 +69,13 @@ import org.eclipse.jgit.diff.HistogramDiff;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.diff.RenameDetector;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.dircache.DirCacheIterator;
+import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
@@ -74,9 +85,12 @@ import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.eclipse.jgit.util.IO;
/**
* Generate author information for lines based on a provided file.
@@ -313,6 +327,107 @@ public class BlameGenerator implements AutoCloseable {
}
/**
+ * Pushes HEAD, index, and working tree as appropriate for blaming the file
+ * given in the constructor {@link #BlameGenerator(Repository, String)}
+ * against HEAD. Includes special handling in case the file is in conflict
+ * state from an unresolved merge conflict.
+ *
+ * @return {@code this}
+ * @throws NoHeadException
+ * if the repository has no HEAD
+ * @throws IOException
+ * if an error occurs
+ * @since 5.6
+ */
+ public BlameGenerator prepareHead() throws NoHeadException, IOException {
+ Repository repo = getRepository();
+ ObjectId head = repo.resolve(Constants.HEAD);
+ if (head == null) {
+ throw new NoHeadException(MessageFormat
+ .format(JGitText.get().noSuchRefKnown, Constants.HEAD));
+ }
+ if (repo.isBare()) {
+ return push(null, head);
+ }
+ DirCache dc = repo.readDirCache();
+ try (TreeWalk walk = new TreeWalk(repo)) {
+ walk.setOperationType(OperationType.CHECKIN_OP);
+ FileTreeIterator iter = new FileTreeIterator(repo);
+ int fileTree = walk.addTree(iter);
+ int indexTree = walk.addTree(new DirCacheIterator(dc));
+ iter.setDirCacheIterator(walk, indexTree);
+ walk.setFilter(resultPath);
+ walk.setRecursive(true);
+ if (!walk.next()) {
+ return this;
+ }
+ DirCacheIterator dcIter = walk.getTree(indexTree,
+ DirCacheIterator.class);
+ if (dcIter == null) {
+ // Not found in index
+ return this;
+ }
+ iter = walk.getTree(fileTree, FileTreeIterator.class);
+ if (iter == null || !isFile(iter.getEntryRawMode())) {
+ return this;
+ }
+ RawText inTree;
+ long filteredLength = iter.getEntryContentLength();
+ try (InputStream stream = iter.openEntryStream()) {
+ inTree = new RawText(getBytes(iter.getEntryFile().getPath(),
+ stream, filteredLength));
+ }
+ DirCacheEntry indexEntry = dcIter.getDirCacheEntry();
+ if (indexEntry.getStage() == DirCacheEntry.STAGE_0) {
+ push(null, head);
+ push(null, indexEntry.getObjectId());
+ push(null, inTree);
+ } else {
+ // Create a special candidate using the working tree file as
+ // blob and HEAD and the MERGE_HEADs as parents.
+ HeadCandidate c = new HeadCandidate(getRepository(), resultPath,
+ getHeads(repo, head));
+ c.sourceText = inTree;
+ c.regionList = new Region(0, 0, inTree.size());
+ remaining = inTree.size();
+ push(c);
+ }
+ }
+ return this;
+ }
+
+ private List<RevCommit> getHeads(Repository repo, ObjectId head)
+ throws NoWorkTreeException, IOException {
+ List<ObjectId> mergeIds = repo.readMergeHeads();
+ if (mergeIds == null || mergeIds.isEmpty()) {
+ return Collections.singletonList(revPool.parseCommit(head));
+ }
+ List<RevCommit> heads = new ArrayList<>(mergeIds.size() + 1);
+ heads.add(revPool.parseCommit(head));
+ for (ObjectId id : mergeIds) {
+ heads.add(revPool.parseCommit(id));
+ }
+ return heads;
+ }
+
+ private static byte[] getBytes(String path, InputStream in, long maxLength)
+ throws IOException {
+ if (maxLength > Integer.MAX_VALUE) {
+ throw new IOException(
+ MessageFormat.format(JGitText.get().fileIsTooLarge, path));
+ }
+ int max = (int) maxLength;
+ byte[] buffer = new byte[max];
+ int read = IO.readFully(in, buffer, 0);
+ if (read == max) {
+ return buffer;
+ }
+ byte[] copy = new byte[read];
+ System.arraycopy(buffer, 0, copy, 0, read);
+ return copy;
+ }
+
+ /**
* Push a candidate object onto the generator's traversal stack.
* <p>
* Candidates should be pushed in history order from oldest-to-newest.
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 5fb77501fa..394aba6a59 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java
@@ -267,18 +267,18 @@ public class BlameResult {
*/
public int computeNext() throws IOException {
BlameGenerator gen = generator;
- if (gen == null)
+ if (gen == null) {
return -1;
+ }
if (gen.next()) {
loadFrom(gen);
lastLength = gen.getRegionLength();
return gen.getResultStart();
- } else {
- gen.close();
- generator = null;
- return -1;
}
+ gen.close();
+ generator = null;
+ return -1;
}
/**
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 457d1d2cea..3ef4943982 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/blame/Candidate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/Candidate.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011, Google Inc.
+ * Copyright (C) 2011, 2019 Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -44,12 +44,14 @@
package org.eclipse.jgit.blame;
import java.io.IOException;
+import java.util.List;
import org.eclipse.jgit.blame.ReverseWalk.ReverseCommit;
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.diff.EditList;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
@@ -392,6 +394,66 @@ class Candidate {
}
/**
+ * A {@link Candidate} to blame a working tree file in conflict state.
+ * <p>
+ * Contrary to {@link BlobCandidate}, it expects to be given the parent
+ * commits (typically HEAD and the MERGE_HEADs) and behaves like a merge
+ * commit during blame. It does <em>not</em> consider a previously pushed
+ * Candidate as its parent.
+ * </p>
+ */
+ static final class HeadCandidate extends Candidate {
+
+ private List<RevCommit> parents;
+
+ HeadCandidate(Repository repo, PathFilter path,
+ List<RevCommit> parents) {
+ super(repo, null, path);
+ this.parents = parents;
+ }
+
+ @Override
+ void beginResult(RevWalk rw) {
+ // Blob candidates have nothing to prepare.
+ }
+
+ @Override
+ int getParentCount() {
+ return parents.size();
+ }
+
+ @Override
+ RevCommit getParent(int idx) {
+ return parents.get(idx);
+ }
+
+ @Override
+ boolean has(RevFlag flag) {
+ return true; // Pretend flag was added; sourceCommit is null.
+ }
+
+ @Override
+ void add(RevFlag flag) {
+ // Do nothing, sourceCommit is null.
+ }
+
+ @Override
+ void remove(RevFlag flag) {
+ // Do nothing, sourceCommit is null.
+ }
+
+ @Override
+ int getTime() {
+ return Integer.MAX_VALUE;
+ }
+
+ @Override
+ PersonIdent getAuthor() {
+ return new PersonIdent(JGitText.get().blameNotCommittedYet, ""); //$NON-NLS-1$
+ }
+ }
+
+ /**
* Candidate loaded from a file source, and not a commit.
* <p>
* The {@link Candidate#sourceCommit} field is always null on this type of
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java
index ca37a10c5a..9071aeb09c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffConfig.java
@@ -59,7 +59,7 @@ public class DiffConfig {
public static final Config.SectionParser<DiffConfig> KEY = DiffConfig::new;
/** Permissible values for {@code diff.renames}. */
- public static enum RenameDetectionType {
+ public enum RenameDetectionType {
/** Rename detection is disabled. */
FALSE,
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 5c8343f92c..9f660ec1c8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java
@@ -72,7 +72,7 @@ public class DiffEntry {
public static final String DEV_NULL = "/dev/null"; //$NON-NLS-1$
/** General type of change a single file-level patch describes. */
- public static enum ChangeType {
+ public enum ChangeType {
/** Add a new file to the project */
ADD,
@@ -90,7 +90,7 @@ public class DiffEntry {
}
/** Specify the old or new side for more generalized access. */
- public static enum Side {
+ public enum Side {
/** The old side of a DiffEntry. */
OLD,
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 1cecff6fb0..d764499159 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
@@ -145,6 +145,8 @@ public class DiffFormatter implements AutoCloseable {
private Repository repository;
+ private Boolean quotePaths;
+
/**
* Create a new formatter with a default level of context.
*
@@ -199,6 +201,11 @@ public class DiffFormatter implements AutoCloseable {
this.closeReader = closeReader;
this.reader = reader;
this.diffCfg = cfg.get(DiffConfig.KEY);
+ if (quotePaths == null) {
+ quotePaths = Boolean
+ .valueOf(cfg.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
+ ConfigConstants.CONFIG_KEY_QUOTE_PATH, true));
+ }
ContentSource cs = ContentSource.create(reader);
source = new ContentSource.Pair(cs, cs);
@@ -379,6 +386,21 @@ public class DiffFormatter implements AutoCloseable {
}
/**
+ * Sets whether or not path names should be quoted.
+ * <p>
+ * By default the setting of git config {@code core.quotePath} is active,
+ * but this can be overridden through this method.
+ * </p>
+ *
+ * @param quote
+ * whether to quote path names
+ * @since 5.6
+ */
+ public void setQuotePaths(boolean quote) {
+ quotePaths = Boolean.valueOf(quote);
+ }
+
+ /**
* Set the filter to produce only specific paths.
*
* If the filter is an instance of
@@ -489,8 +511,8 @@ public class DiffFormatter implements AutoCloseable {
CanonicalTreeParser parser = new CanonicalTreeParser();
parser.reset(reader, tree);
return parser;
- } else
- return new EmptyTreeIterator();
+ }
+ return new EmptyTreeIterator();
}
/**
@@ -726,8 +748,11 @@ public class DiffFormatter implements AutoCloseable {
return id.name();
}
- private static String quotePath(String name) {
- return QuotedString.GIT_PATH.quote(name);
+ private String quotePath(String path) {
+ if (quotePaths == null || quotePaths.booleanValue()) {
+ return QuotedString.GIT_PATH.quote(path);
+ }
+ return QuotedString.GIT_PATH_MINIMAL.quote(path);
}
/**
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 5c876e87fd..b4e09636c7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/Edit.java
@@ -64,7 +64,7 @@ package org.eclipse.jgit.diff;
*/
public class Edit {
/** Type of edit */
- public static enum Type {
+ public enum Type {
/** Sequence B has inserted the region. */
INSERT,
@@ -125,17 +125,17 @@ public class Edit {
*/
public final Type getType() {
if (beginA < endA) {
- if (beginB < endB)
+ if (beginB < endB) {
return Type.REPLACE;
- else /* if (beginB == endB) */
- return Type.DELETE;
-
- } else /* if (beginA == endA) */{
- if (beginB < endB)
- return Type.INSERT;
- else /* if (beginB == endB) */
- return Type.EMPTY;
+ }
+ return Type.DELETE;
+
+ }
+ if (beginB < endB) {
+ return Type.INSERT;
}
+ // beginB == endB)
+ return Type.EMPTY;
}
/**
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 6c0d90ebad..4e79fc9386 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java
@@ -383,15 +383,17 @@ public class RawText extends Sequence {
* @return the line delimiter or <code>null</code>
*/
public String getLineDelimiter() {
- if (size() == 0)
+ if (size() == 0) {
return null;
+ }
int e = getEnd(0);
- if (content[e - 1] != '\n')
+ if (content[e - 1] != '\n') {
return null;
- if (content.length > 1 && e > 1 && content[e - 2] == '\r')
+ }
+ if (content.length > 1 && e > 1 && content[e - 2] == '\r') {
return "\r\n"; //$NON-NLS-1$
- else
- return "\n"; //$NON-NLS-1$
+ }
+ return "\n"; //$NON-NLS-1$
}
/**
@@ -446,7 +448,7 @@ public class RawText extends Sequence {
}
}
- byte data[];
+ byte[] data;
try {
data = new byte[(int)sz];
} catch (OutOfMemoryError e) {
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 d8a05c34ea..1b7babcecb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/SimilarityRenameDetector.java
@@ -335,14 +335,14 @@ class SimilarityRenameDetector {
}
static int nameScore(String a, String b) {
- int aDirLen = a.lastIndexOf("/") + 1; //$NON-NLS-1$
- int bDirLen = b.lastIndexOf("/") + 1; //$NON-NLS-1$
+ int aDirLen = a.lastIndexOf('/') + 1;
+ int bDirLen = b.lastIndexOf('/') + 1;
- int dirMin = Math.min(aDirLen, bDirLen);
- int dirMax = Math.max(aDirLen, bDirLen);
+ int dirMin = Math.min(aDirLen, bDirLen);
+ int dirMax = Math.max(aDirLen, bDirLen);
- final int dirScoreLtr;
- final int dirScoreRtl;
+ final int dirScoreLtr;
+ final int dirScoreRtl;
if (dirMax == 0) {
dirScoreLtr = 100;
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 6bc2946078..3502d7a43a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -53,9 +53,11 @@ import java.text.MessageFormat;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.api.errors.FilterFailedException;
@@ -104,7 +106,8 @@ import org.slf4j.LoggerFactory;
* This class handles checking out one or two trees merging with the index.
*/
public class DirCacheCheckout {
- private static Logger LOG = LoggerFactory.getLogger(DirCacheCheckout.class);
+ private static final Logger LOG = LoggerFactory
+ .getLogger(DirCacheCheckout.class);
private static final int MAX_EXCEPTION_TEXT_SIZE = 10 * 1024;
@@ -142,6 +145,8 @@ public class DirCacheCheckout {
private ArrayList<String> removed = new ArrayList<>();
+ private ArrayList<String> kept = new ArrayList<>();
+
private ObjectId mergeCommitTree;
private DirCache dc;
@@ -432,11 +437,11 @@ public class DirCacheCheckout {
if (mtime == null || mtime.equals(Instant.EPOCH)) {
entry.setLastModified(f.getEntryLastModifiedInstant());
}
- keep(entry, f);
+ keep(i.getEntryPathString(), entry, f);
}
} else
// The index contains a folder
- keep(i.getDirCacheEntry(), f);
+ 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
@@ -496,8 +501,11 @@ public class DirCacheCheckout {
dc.unlock();
} finally {
if (performingCheckout) {
+ Set<String> touched = new HashSet<>(conflicts);
+ touched.addAll(getUpdated().keySet());
+ touched.addAll(kept);
WorkingTreeModifiedEvent event = new WorkingTreeModifiedEvent(
- getUpdated().keySet(), getRemoved());
+ touched, getRemoved());
if (!event.isEmpty()) {
repo.fireEvent(event);
}
@@ -517,10 +525,10 @@ public class DirCacheCheckout {
prescanOneTree();
if (!conflicts.isEmpty()) {
- if (failOnConflict)
+ if (failOnConflict) {
throw new CheckoutConflictException(conflicts.toArray(new String[0]));
- else
- cleanUpConflicts();
+ }
+ cleanUpConflicts();
}
// update our index
@@ -826,14 +834,14 @@ public class DirCacheCheckout {
break;
case 0xDFD: // 3 4
- keep(dce, f);
+ keep(name, dce, f);
break;
case 0xF0D: // 18
remove(name);
break;
case 0xDFF: // 5 5b 6 6b
if (equalIdAndMode(iId, iMode, mId, mMode))
- keep(dce, f); // 5 6
+ keep(name, dce, f); // 5 6
else
conflict(name, dce, h, m); // 5b 6b
break;
@@ -863,7 +871,7 @@ public class DirCacheCheckout {
conflict(name, dce, h, m); // 9
break;
case 0xFD0: // keep without a rule
- keep(dce, f);
+ keep(name, dce, f);
break;
case 0xFFD: // 12 13 14
if (equalIdAndMode(hId, hMode, iId, iMode))
@@ -883,7 +891,7 @@ public class DirCacheCheckout {
conflict(name, dce, h, m);
break;
default:
- keep(dce, f);
+ keep(name, dce, f);
}
return;
}
@@ -895,15 +903,14 @@ public class DirCacheCheckout {
// the workingtree entry doesn't exist or also contains a folder
// -> no problem
return;
- } else {
- // the workingtree entry exists and is not a folder
- if (!idEqual(h, m)) {
- // Because HEAD and MERGE differ we will try to update the
- // workingtree with a folder -> return a conflict
- conflict(name, null, null, null);
- }
- return;
}
+ // the workingtree entry exists and is not a folder
+ if (!idEqual(h, m)) {
+ // Because HEAD and MERGE differ we will try to update the
+ // workingtree with a folder -> return a conflict
+ conflict(name, null, null, null);
+ }
+ return;
}
if ((ffMask == 0x00F) && f != null && FileMode.TREE.equals(f.getEntryFileMode())) {
@@ -969,7 +976,7 @@ public class DirCacheCheckout {
if (initialCheckout)
update(name, mId, mMode);
else
- keep(dce, f);
+ keep(name, dce, f);
} else
conflict(name, dce, h, m);
}
@@ -1032,7 +1039,7 @@ public class DirCacheCheckout {
// Nothing in Head
// Something in Index
// -> Merge contains nothing new. Keep the index.
- keep(dce, f);
+ keep(name, dce, f);
} else
// Merge contains something and it is not the same as Index
// Nothing in Head
@@ -1083,15 +1090,15 @@ public class DirCacheCheckout {
// Something in Head
if (!FileMode.TREE.equals(f.getEntryFileMode())
- && FileMode.TREE.equals(iMode))
+ && FileMode.TREE.equals(iMode)) {
// The workingtree contains a file and the index semantically contains a folder.
// Git considers the workingtree file as untracked. Just keep the untracked file.
return;
- else
- // -> file is dirty and tracked but is should be
- // removed. That's a conflict
- conflict(name, dce, h, m);
- } else
+ }
+ // -> file is dirty and tracked but is should be
+ // removed. That's a conflict
+ conflict(name, dce, h, m);
+ } else {
// file doesn't exist or is clean
// Index contains the same as Head
// Something different from a submodule in Index
@@ -1099,7 +1106,8 @@ public class DirCacheCheckout {
// Something in Head
// -> Remove from index and delete the file
remove(name);
- } else
+ }
+ } else {
// Index contains something different from Head
// Something different from a submodule in Index
// Nothing in Merge
@@ -1108,6 +1116,7 @@ public class DirCacheCheckout {
// filesystem). But Merge wants the path to be removed.
// Report a conflict
conflict(name, dce, h, m);
+ }
}
} else {
// Something in Merge
@@ -1181,7 +1190,7 @@ public class DirCacheCheckout {
// to the other one.
// -> In all three cases we don't touch index and file.
- keep(dce, f);
+ keep(name, dce, f);
}
}
}
@@ -1230,13 +1239,17 @@ public class DirCacheCheckout {
}
}
- private void keep(DirCacheEntry e, WorkingTreeIterator f)
+ private void keep(String path, DirCacheEntry e, WorkingTreeIterator f)
throws IOException {
if (e != null && !FileMode.TREE.equals(e.getFileMode()))
builder.add(e);
if (force) {
- if (f.isModified(e, true, this.walk.getObjectReader())) {
- checkoutEntry(repo, e, this.walk.getObjectReader());
+ if (f.isModified(e, true, walk.getObjectReader())) {
+ kept.add(path);
+ checkoutEntry(repo, e, walk.getObjectReader(), false,
+ new CheckoutMetadata(walk.getEolStreamType(CHECKOUT_OP),
+ walk.getFilterCommand(
+ Constants.ATTR_FILTER_TYPE_SMUDGE)));
}
}
}
@@ -1340,13 +1353,14 @@ public class DirCacheCheckout {
private boolean isModified_IndexTree(String path, ObjectId iId,
FileMode iMode, ObjectId tId, FileMode tMode, ObjectId rootTree)
throws CorruptObjectException, IOException {
- if (iMode != tMode)
+ if (iMode != tMode) {
return true;
+ }
if (FileMode.TREE.equals(iMode)
- && (iId == null || ObjectId.zeroId().equals(iId)))
+ && (iId == null || ObjectId.zeroId().equals(iId))) {
return isModifiedSubtree_IndexTree(path, rootTree);
- else
- return !equalIdAndMode(iId, iMode, tId, tMode);
+ }
+ return !equalIdAndMode(iId, iMode, tId, tMode);
}
/**
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 0e91f0d748..cbf96e468c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
@@ -824,10 +824,10 @@ public class DirCacheEntry {
}
private int getExtendedFlags() {
- if (isExtended())
+ if (isExtended()) {
return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16;
- else
- return 0;
+ }
+ return 0;
}
private static void checkPath(byte[] path) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoMergeBaseException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoMergeBaseException.java
index 97214b0ede..023dfe9657 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoMergeBaseException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/NoMergeBaseException.java
@@ -63,7 +63,7 @@ public class NoMergeBaseException extends IOException {
* An enum listing the different reason why no merge base could be
* determined.
*/
- public static enum MergeBaseFailureReason {
+ public enum MergeBaseFailureReason {
/**
* Multiple merge bases have been found (e.g. the commits to be merged
* have multiple common predecessors) but the merge strategy doesn't
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleException.java
index 70760f36bf..6d18c7c512 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/TranslationBundleException.java
@@ -78,7 +78,7 @@ public abstract class TranslationBundleException extends RuntimeException {
*
* @return bundle class for which the exception occurred
*/
- final public Class getBundleClass() {
+ public final Class getBundleClass() {
return bundleClass;
}
@@ -87,7 +87,7 @@ public abstract class TranslationBundleException extends RuntimeException {
*
* @return locale for which the exception occurred
*/
- final public Locale getLocale() {
+ public final Locale getLocale() {
return locale;
}
}
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 60669bb955..2c93d55f4d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/AbstractHead.java
@@ -82,10 +82,10 @@ abstract class AbstractHead implements Head {
/** {@inheritDoc} */
@Override
public List<Head> getNextHeads(char c) {
- if (matches(c))
+ if (matches(c)) {
return newHeads;
- else
- return FileNameMatcher.EMPTY_HEAD_LIST;
+ }
+ return FileNameMatcher.EMPTY_HEAD_LIST;
}
boolean isStar() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java
index bfcc580ba4..8125c356a6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/fnmatch/FileNameMatcher.java
@@ -304,11 +304,11 @@ public class FileNameMatcher {
private static AbstractHead createWildCardHead(
final Character invalidWildgetCharacter, final boolean star) {
- if (invalidWildgetCharacter != null)
+ if (invalidWildgetCharacter != null) {
return new RestrictedWildCardHead(invalidWildgetCharacter
.charValue(), star);
- else
- return new WildCardHead(star);
+ }
+ return new WildCardHead(star);
}
/**
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 8e463415b8..febdb92091 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
@@ -293,13 +293,12 @@ public class ManifestParser extends DefaultHandler {
String revision = defaultRevision;
if (remote == null) {
if (defaultRemote == null) {
- if (filename != null)
+ if (filename != null) {
throw new SAXException(MessageFormat.format(
RepoText.get().errorNoDefaultFilename,
filename));
- else
- throw new SAXException(
- RepoText.get().errorNoDefault);
+ }
+ throw new SAXException(RepoText.get().errorNoDefault);
}
remote = defaultRemote;
} else {
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 cb62925a1f..7288678007 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -761,18 +761,17 @@ public class RepoCommand extends GitCommand<RevCommit> {
} catch (GitAPIException | IOException e) {
throw new ManifestErrorException(e);
}
- } else {
- try (Git git = new Git(repo)) {
- for (RepoProject proj : filteredProjects) {
- addSubmodule(proj.getName(), proj.getUrl(), proj.getPath(),
- proj.getRevision(), proj.getCopyFiles(),
- proj.getLinkFiles(), git);
- }
- return git.commit().setMessage(RepoText.get().repoCommitMessage)
- .call();
- } catch (GitAPIException | IOException e) {
- throw new ManifestErrorException(e);
+ }
+ try (Git git = new Git(repo)) {
+ for (RepoProject proj : filteredProjects) {
+ addSubmodule(proj.getName(), proj.getUrl(), proj.getPath(),
+ proj.getRevision(), proj.getCopyFiles(),
+ proj.getLinkFiles(), git);
}
+ return git.commit().setMessage(RepoText.get().repoCommitMessage)
+ .call();
+ } catch (GitAPIException | IOException e) {
+ throw new ManifestErrorException(e);
}
}
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 d79dfa8b2f..684d1e457f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
@@ -422,10 +422,10 @@ public class RepoProject implements Comparable<RepoProject> {
}
private String getPathWithSlash() {
- if (path.endsWith("/")) //$NON-NLS-1$
+ if (path.endsWith("/")) { //$NON-NLS-1$
return path;
- else
- return path + "/"; //$NON-NLS-1$
+ }
+ return path + "/"; //$NON-NLS-1$
}
/**
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 f33168d814..6dbe0a6609 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/CommitMsgHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/CommitMsgHook.java
@@ -72,6 +72,9 @@ public class CommitMsgHook extends GitHook<String> {
/**
* Constructor for CommitMsgHook
+ * <p>
+ * This constructor will use the default error stream.
+ * </p>
*
* @param repo
* The repository
@@ -83,6 +86,24 @@ public class CommitMsgHook extends GitHook<String> {
super(repo, outputStream);
}
+ /**
+ * Constructor for CommitMsgHook
+ *
+ * @param repo
+ * The repository
+ * @param outputStream
+ * The output stream the hook must use. {@code null} is allowed,
+ * in which case the hook will use {@code System.out}.
+ * @param errorStream
+ * The error stream the hook must use. {@code null} is allowed,
+ * in which case the hook will use {@code System.err}.
+ * @since 5.6
+ */
+ protected CommitMsgHook(Repository repo, PrintStream outputStream,
+ PrintStream errorStream) {
+ super(repo, outputStream, errorStream);
+ }
+
/** {@inheritDoc} */
@Override
public String call() throws IOException, AbortedByHookException {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
index ad43e2ca83..aa307c9378 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
@@ -50,6 +50,7 @@ import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.Callable;
+import org.bouncycastle.util.io.TeeOutputStream;
import org.eclipse.jgit.api.errors.AbortedByHookException;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FS;
@@ -79,7 +80,15 @@ abstract class GitHook<T> implements Callable<T> {
protected final PrintStream outputStream;
/**
- * Constructor for GitHook
+ * The error stream to be used by the hook.
+ */
+ protected final PrintStream errorStream;
+
+ /**
+ * Constructor for GitHook.
+ * <p>
+ * This constructor will use stderr for the error stream.
+ * </p>
*
* @param repo
* a {@link org.eclipse.jgit.lib.Repository} object.
@@ -88,8 +97,26 @@ abstract class GitHook<T> implements Callable<T> {
* in which case the hook will use {@code System.out}.
*/
protected GitHook(Repository repo, PrintStream outputStream) {
+ this(repo, outputStream, null);
+ }
+
+ /**
+ * Constructor for GitHook
+ *
+ * @param repo
+ * a {@link org.eclipse.jgit.lib.Repository} object.
+ * @param outputStream
+ * The output stream the hook must use. {@code null} is allowed,
+ * in which case the hook will use {@code System.out}.
+ * @param errorStream
+ * The error stream the hook must use. {@code null} is allowed,
+ * in which case the hook will use {@code System.err}.
+ */
+ protected GitHook(Repository repo, PrintStream outputStream,
+ PrintStream errorStream) {
this.repo = repo;
this.outputStream = outputStream;
+ this.errorStream = errorStream;
}
/**
@@ -148,6 +175,16 @@ abstract class GitHook<T> implements Callable<T> {
}
/**
+ * Get error stream
+ *
+ * @return The error stream the hook must use. Never {@code null},
+ * {@code System.err} is returned by default.
+ */
+ protected PrintStream getErrorStream() {
+ return errorStream == null ? System.err : errorStream;
+ }
+
+ /**
* Runs the hook, without performing any validity checks.
*
* @throws org.eclipse.jgit.api.errors.AbortedByHookException
@@ -155,16 +192,23 @@ abstract class GitHook<T> implements Callable<T> {
*/
protected void doRun() throws AbortedByHookException {
final ByteArrayOutputStream errorByteArray = new ByteArrayOutputStream();
+ final TeeOutputStream stderrStream = new TeeOutputStream(errorByteArray,
+ getErrorStream());
PrintStream hookErrRedirect = null;
try {
- hookErrRedirect = new PrintStream(errorByteArray, false,
+ hookErrRedirect = new PrintStream(stderrStream, false,
UTF_8.name());
} catch (UnsupportedEncodingException e) {
// UTF-8 is guaranteed to be available
}
- ProcessResult result = FS.DETECTED.runHookIfPresent(getRepository(),
- getHookName(), getParameters(), getOutputStream(),
- hookErrRedirect, getStdinArgs());
+ Repository repository = getRepository();
+ FS fs = repository.getFS();
+ if (fs == null) {
+ fs = FS.DETECTED;
+ }
+ ProcessResult result = fs.runHookIfPresent(repository, getHookName(),
+ getParameters(), getOutputStream(), hookErrRedirect,
+ getStdinArgs());
if (result.isExecutedWithError()) {
throw new AbortedByHookException(
new String(errorByteArray.toByteArray(), UTF_8),
@@ -180,7 +224,11 @@ abstract class GitHook<T> implements Callable<T> {
* @since 4.11
*/
public boolean isNativeHookPresent() {
- return FS.DETECTED.findHook(getRepository(), getHookName()) != null;
+ FS fs = getRepository().getFS();
+ if (fs == null) {
+ fs = FS.DETECTED;
+ }
+ return fs.findHook(getRepository(), getHookName()) != null;
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java
index b801d6872b..f29dcd178b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java
@@ -57,7 +57,8 @@ import org.eclipse.jgit.util.LfsFactory;
public class Hooks {
/**
- * Create pre-commit hook for the given repository
+ * Create pre-commit hook for the given repository with the default error
+ * stream
*
* @param repo
* a {@link org.eclipse.jgit.lib.Repository} object.
@@ -71,7 +72,25 @@ public class Hooks {
}
/**
- * Create post-commit hook for the given repository
+ * Create pre-commit hook for the given repository
+ *
+ * @param repo
+ * a {@link org.eclipse.jgit.lib.Repository} object.
+ * @param outputStream
+ * The output stream, or {@code null} to use {@code System.out}
+ * @param errorStream
+ * The error stream, or {@code null} to use {@code System.err}
+ * @return The pre-commit hook for the given repository.
+ * @since 5.6
+ */
+ public static PreCommitHook preCommit(Repository repo,
+ PrintStream outputStream, PrintStream errorStream) {
+ return new PreCommitHook(repo, outputStream, errorStream);
+ }
+
+ /**
+ * Create post-commit hook for the given repository with the default error
+ * stream
*
* @param repo
* a {@link org.eclipse.jgit.lib.Repository} object.
@@ -86,7 +105,25 @@ public class Hooks {
}
/**
- * Create commit-msg hook for the given repository
+ * Create post-commit hook for the given repository
+ *
+ * @param repo
+ * a {@link org.eclipse.jgit.lib.Repository} object.
+ * @param outputStream
+ * The output stream, or {@code null} to use {@code System.out}
+ * @param errorStream
+ * The error stream, or {@code null} to use {@code System.err}
+ * @return The pre-commit hook for the given repository.
+ * @since 5.6
+ */
+ public static PostCommitHook postCommit(Repository repo,
+ PrintStream outputStream, PrintStream errorStream) {
+ return new PostCommitHook(repo, outputStream, errorStream);
+ }
+
+ /**
+ * Create commit-msg hook for the given repository with the default error
+ * stream
*
* @param repo
* a {@link org.eclipse.jgit.lib.Repository} object.
@@ -100,7 +137,25 @@ public class Hooks {
}
/**
- * Create pre-push hook for the given repository
+ * Create commit-msg hook for the given repository
+ *
+ * @param repo
+ * a {@link org.eclipse.jgit.lib.Repository} object.
+ * @param outputStream
+ * The output stream, or {@code null} to use {@code System.out}
+ * @param errorStream
+ * The error stream, or {@code null} to use {@code System.err}
+ * @return The pre-commit hook for the given repository.
+ * @since 5.6
+ */
+ public static CommitMsgHook commitMsg(Repository repo,
+ PrintStream outputStream, PrintStream errorStream) {
+ return new CommitMsgHook(repo, outputStream, errorStream);
+ }
+
+ /**
+ * Create pre-push hook for the given repository with the default error
+ * stream
*
* @param repo
* a {@link org.eclipse.jgit.lib.Repository} object.
@@ -127,4 +182,36 @@ public class Hooks {
}
return new PrePushHook(repo, outputStream);
}
+
+ /**
+ * Create pre-push hook for the given repository
+ *
+ * @param repo
+ * a {@link org.eclipse.jgit.lib.Repository} object.
+ * @param outputStream
+ * The output stream, or {@code null} to use {@code System.out}
+ * @param errorStream
+ * The error stream, or {@code null} to use {@code System.err}
+ * @return The pre-push hook for the given repository.
+ * @since 5.6
+ */
+ public static PrePushHook prePush(Repository repo, PrintStream outputStream,
+ PrintStream errorStream) {
+ if (LfsFactory.getInstance().isAvailable()) {
+ PrePushHook hook = LfsFactory.getInstance().getPrePushHook(repo,
+ outputStream, errorStream);
+ if (hook != null) {
+ if (hook.isNativeHookPresent()) {
+ PrintStream ps = outputStream;
+ if (ps == null) {
+ ps = System.out;
+ }
+ ps.println(MessageFormat
+ .format(JGitText.get().lfsHookConflict, repo));
+ }
+ return hook;
+ }
+ }
+ return new PrePushHook(repo, outputStream, errorStream);
+ }
}
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 24bad16ecb..b6e576fc23 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PostCommitHook.java
@@ -61,6 +61,9 @@ public class PostCommitHook extends GitHook<Void> {
/**
* Constructor for PostCommitHook
+ * <p>
+ * This constructor will use the default error stream.
+ * </p>
*
* @param repo
* The repository
@@ -72,6 +75,24 @@ public class PostCommitHook extends GitHook<Void> {
super(repo, outputStream);
}
+ /**
+ * Constructor for PostCommitHook
+ *
+ * @param repo
+ * The repository
+ * @param outputStream
+ * The output stream the hook must use. {@code null} is allowed,
+ * in which case the hook will use {@code System.out}.
+ * @param errorStream
+ * The error stream the hook must use. {@code null} is allowed,
+ * in which case the hook will use {@code System.err}.
+ * @since 5.6
+ */
+ protected PostCommitHook(Repository repo, PrintStream outputStream,
+ PrintStream errorStream) {
+ super(repo, outputStream, errorStream);
+ }
+
/** {@inheritDoc} */
@Override
public Void call() throws IOException, AbortedByHookException {
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 0d9290da3e..dbdaf8669c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PreCommitHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PreCommitHook.java
@@ -61,6 +61,9 @@ public class PreCommitHook extends GitHook<Void> {
/**
* Constructor for PreCommitHook
+ * <p>
+ * This constructor will use the default error stream.
+ * </p>
*
* @param repo
* The repository
@@ -72,6 +75,24 @@ public class PreCommitHook extends GitHook<Void> {
super(repo, outputStream);
}
+ /**
+ * Constructor for PreCommitHook
+ *
+ * @param repo
+ * The repository
+ * @param outputStream
+ * The output stream the hook must use. {@code null} is allowed,
+ * in which case the hook will use {@code System.out}.
+ * @param errorStream
+ * The error stream the hook must use. {@code null} is allowed,
+ * in which case the hook will use {@code System.err}.
+ * @since 5.6
+ */
+ protected PreCommitHook(Repository repo, PrintStream outputStream,
+ PrintStream errorStream) {
+ super(repo, outputStream, errorStream);
+ }
+
/** {@inheritDoc} */
@Override
public Void call() throws IOException, AbortedByHookException {
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 431944f9d4..61180fd021 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
@@ -73,6 +73,9 @@ public class PrePushHook extends GitHook<String> {
/**
* Constructor for PrePushHook
+ * <p>
+ * This constructor will use the default error stream.
+ * </p>
*
* @param repo
* The repository
@@ -84,6 +87,24 @@ public class PrePushHook extends GitHook<String> {
super(repo, outputStream);
}
+ /**
+ * Constructor for PrePushHook
+ *
+ * @param repo
+ * The repository
+ * @param outputStream
+ * The output stream the hook must use. {@code null} is allowed,
+ * in which case the hook will use {@code System.out}.
+ * @param errorStream
+ * The error stream the hook must use. {@code null} is allowed,
+ * in which case the hook will use {@code System.err}.
+ * @since 5.6
+ */
+ protected PrePushHook(Repository repo, PrintStream outputStream,
+ PrintStream errorStream) {
+ super(repo, outputStream, errorStream);
+ }
+
/** {@inheritDoc} */
@Override
protected String getStdinArgs() {
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 31e173bf23..4bc926ae11 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
@@ -62,7 +62,7 @@ import org.slf4j.LoggerFactory;
* @since 3.6
*/
public class FastIgnoreRule {
- private final static Logger LOG = LoggerFactory
+ private static final Logger LOG = LoggerFactory
.getLogger(FastIgnoreRule.class);
/**
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 864f8bfc02..d47ffc21a6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
@@ -59,7 +59,7 @@ import java.util.List;
*/
public class IgnoreNode {
/** Result from {@link IgnoreNode#isIgnored(String, boolean)}. */
- public static enum MatchResult {
+ public enum MatchResult {
/** The file is not ignored, due to a rule saying its not ignored. */
NOT_IGNORED,
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 3c0f17ab3d..b7d6acce14 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
@@ -291,26 +291,25 @@ public class PathMatcher extends AbstractMatcher {
// We had a prefix match here.
if (!pathMatch) {
return true;
+ }
+ if (right == endExcl - 1) {
+ // Extra slash at the end: actually a full match.
+ // Must meet directory expectations
+ return !dirOnly || assumeDirectory;
+ }
+ // Prefix matches only if pattern ended with /**
+ if (wasWild) {
+ return true;
+ }
+ if (lastWildmatch >= 0) {
+ // Consider pattern **/x and input x/x.
+ // We've matched the prefix x/ so far: we
+ // must try to extend the **!
+ matcher = lastWildmatch + 1;
+ right = wildmatchBacktrackPos;
+ wildmatchBacktrackPos = -1;
} else {
- if (right == endExcl - 1) {
- // Extra slash at the end: actually a full match.
- // Must meet directory expectations
- return !dirOnly || assumeDirectory;
- }
- // Prefix matches only if pattern ended with /**
- if (wasWild) {
- return true;
- }
- if (lastWildmatch >= 0) {
- // Consider pattern **/x and input x/x.
- // We've matched the prefix x/ so far: we
- // must try to extend the **!
- matcher = lastWildmatch + 1;
- right = wildmatchBacktrackPos;
- wildmatchBacktrackPos = -1;
- } else {
- return false;
- }
+ return false;
}
}
} else if (lastWildmatch != -1) {
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 41923eed18..132109b6cc 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
@@ -192,22 +192,20 @@ public class Strings {
}
if (pattern.indexOf('?') != -1) {
return true;
- } else {
- // check if the backslash escapes one of the glob special characters
- // if not, backslash is not part of a regex and treated literally
- int backSlash = pattern.indexOf('\\');
- if (backSlash >= 0) {
- int nextIdx = backSlash + 1;
- if (pattern.length() == nextIdx) {
- return false;
- }
- char nextChar = pattern.charAt(nextIdx);
- if (escapedByBackslash(nextChar)) {
- return true;
- } else {
- return false;
- }
+ }
+ // check if the backslash escapes one of the glob special characters
+ // if not, backslash is not part of a regex and treated literally
+ int backSlash = pattern.indexOf('\\');
+ if (backSlash >= 0) {
+ int nextIdx = backSlash + 1;
+ if (pattern.length() == nextIdx) {
+ return false;
+ }
+ char nextChar = pattern.charAt(nextIdx);
+ if (escapedByBackslash(nextChar)) {
+ return true;
}
+ return false;
}
return false;
}
@@ -231,11 +229,11 @@ public class Strings {
return PatternState.COMPLEX;
}
- static enum PatternState {
+ enum PatternState {
LEADING_ASTERISK_ONLY, TRAILING_ASTERISK_ONLY, COMPLEX, NONE
}
- final static List<String> POSIX_CHAR_CLASSES = Arrays.asList(
+ static final List<String> POSIX_CHAR_CLASSES = Arrays.asList(
"alnum", "alpha", "blank", "cntrl", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
// [:alnum:] [:alpha:] [:blank:] [:cntrl:]
"digit", "graph", "lower", "print", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
@@ -250,7 +248,7 @@ public class Strings {
private static final String DL = "\\p{javaDigit}\\p{javaLetter}"; //$NON-NLS-1$
- final static List<String> JAVA_CHAR_CLASSES = Arrays
+ static final List<String> JAVA_CHAR_CLASSES = Arrays
.asList("\\p{Alnum}", "\\p{javaLetter}", "\\p{Blank}", "\\p{Cntrl}", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
// [:alnum:] [:alpha:] [:blank:] [:cntrl:]
"\\p{javaDigit}", "[\\p{Graph}" + DL + "]", "\\p{Ll}", "[\\p{Print}" + DL + "]", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
@@ -263,7 +261,7 @@ public class Strings {
// Collating symbols [[.a.]] or equivalence class expressions [[=a=]] are
// not supported by CLI git (at least not by 1.9.1)
- final static Pattern UNSUPPORTED = Pattern
+ static final Pattern UNSUPPORTED = Pattern
.compile("\\[\\[[.=]\\w+[.=]\\]\\]"); //$NON-NLS-1$
/**
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 cd7d1e5a4d..37a7c7d93c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -1,45 +1,12 @@
/*
* Copyright (C) 2010, 2013 Sasa Zivkov <sasa.zivkov@sap.com>
- * Copyright (C) 2012, Research In Motion Limited
- * and other copyright owners as documented in the project's IP log.
+ * Copyright (C) 2012, 2021 Research In Motion Limited and others
*
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
+ * 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.
*
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.internal;
@@ -345,10 +312,12 @@ public class JGitText extends TranslationBundle {
/***/ public String failedAtomicFileCreation;
/***/ public String failedCreateLockFile;
/***/ public String failedToDetermineFilterDefinition;
+ /***/ public String failedToConvert;
/***/ public String failedUpdatingRefs;
/***/ public String failureDueToOneOfTheFollowing;
/***/ public String failureUpdatingFETCH_HEAD;
/***/ public String failureUpdatingTrackingRef;
+ /***/ public String fileAlreadyExists;
/***/ public String fileCannotBeDeleted;
/***/ public String fileIsTooLarge;
/***/ public String fileModeNotSetForPath;
@@ -387,6 +356,7 @@ public class JGitText extends TranslationBundle {
/***/ public String incorrectOBJECT_ID_LENGTH;
/***/ public String indexFileCorruptedNegativeBucketCount;
/***/ public String indexFileIsTooLargeForJgit;
+ /***/ public String indexNumbersNotIncreasing;
/***/ public String indexWriteException;
/***/ public String initFailedBareRepoDifferentDirs;
/***/ public String initFailedDirIsNoDirectory;
@@ -412,6 +382,7 @@ public class JGitText extends TranslationBundle {
/***/ public String invalidGitdirRef;
/***/ public String invalidGitModules;
/***/ public String invalidGitType;
+ /***/ public String invalidHooksPath;
/***/ public String invalidId;
/***/ public String invalidId0;
/***/ public String invalidIdLength;
@@ -466,10 +437,13 @@ public class JGitText extends TranslationBundle {
/***/ public String localRefIsMissingObjects;
/***/ public String localRepository;
/***/ public String lockCountMustBeGreaterOrEqual1;
+ /***/ public String lockAlreadyHeld;
/***/ public String lockError;
/***/ public String lockFailedRetry;
/***/ public String lockOnNotClosed;
/***/ public String lockOnNotHeld;
+ /***/ public String lockStreamClosed;
+ /***/ public String lockStreamMultiple;
/***/ public String maxCountMustBeNonNegative;
/***/ public String mergeConflictOnNonNoteEntries;
/***/ public String mergeConflictOnNotes;
@@ -512,8 +486,10 @@ public class JGitText extends TranslationBundle {
/***/ public String noMergeBase;
/***/ public String noMergeHeadSpecified;
/***/ public String nonBareLinkFilesNotSupported;
+ /***/ public String nonCommitToHeads;
/***/ public String noPathAttributesFound;
/***/ public String noSuchRef;
+ /***/ public String noSuchRefKnown;
/***/ public String noSuchSubmodule;
/***/ public String notABoolean;
/***/ public String notABundle;
@@ -612,7 +588,8 @@ public class JGitText extends TranslationBundle {
/***/ public String refAlreadyExists1;
/***/ public String reflogEntryNotFound;
/***/ public String refNotResolved;
- /***/ public String refTableRecordsMustIncrease;
+ /***/ public String reftableDirExists;
+ /***/ public String reftableRecordsMustIncrease;
/***/ public String refUpdateReturnCodeWas;
/***/ public String remoteConfigHasNoURIAssociated;
/***/ public String remoteDoesNotHaveSpec;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java
index c0364acdd1..67cb2f6942 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchLeader.java
@@ -126,7 +126,7 @@ public abstract class KetchLeader {
private static final Logger log = LoggerFactory.getLogger(KetchLeader.class);
/** Current state of the leader instance. */
- public static enum State {
+ public enum State {
/** Newly created instance trying to elect itself leader. */
CANDIDATE,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java
index 0e8377dd02..52c8f29ddc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchReplica.java
@@ -77,6 +77,7 @@ import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.SystemReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -532,9 +533,8 @@ public abstract class KetchReplica {
queued.add(0, new ReplicaPushRequest(this, cmds));
if (!waitingForRetry()) {
- long delay = KetchSystem.delay(
- lastRetryMillis,
- minRetryMillis, maxRetryMillis);
+ long delay = FileUtils
+ .delay(lastRetryMillis, minRetryMillis, maxRetryMillis);
if (log.isDebugEnabled()) {
log.debug("Retrying {} after {} ms", //$NON-NLS-1$
describeForLog(), Long.valueOf(delay));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchSystem.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchSystem.java
index d1d4f67d86..fd334f149a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchSystem.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/KetchSystem.java
@@ -350,25 +350,4 @@ public class KetchSystem {
}
}
- /**
- * Compute a delay in a {@code min..max} interval with random jitter.
- *
- * @param last
- * amount of delay waited before the last attempt. This is used
- * to seed the next delay interval. Should be 0 if there was no
- * prior delay.
- * @param min
- * shortest amount of allowable delay between attempts.
- * @param max
- * longest amount of allowable delay between attempts.
- * @return new amount of delay to wait before the next attempt.
- */
- static long delay(long last, long min, long max) {
- long r = Math.max(0, last * 3 - min);
- if (r > 0) {
- int c = (int) Math.min(r + 1, Integer.MAX_VALUE);
- r = RNG.nextInt(c);
- }
- return Math.max(Math.min(min + r, max), min);
- }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java
index 53fd198006..a27a9bc446 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/ketch/LagCheck.java
@@ -132,9 +132,8 @@ class LagCheck implements AutoCloseable {
// TODO(sop) Check term to see if my leader was deposed.
if (rw.isMergedInto(head, remote)) {
return AHEAD;
- } else {
- return DIVERGENT;
}
+ return DIVERGENT;
} catch (IOException err) {
KetchReplica.log.error(String.format(
"Cannot compare %s", //$NON-NLS-1$
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 c6e2fae42f..16e7a0d537 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
@@ -412,8 +412,7 @@ public final class DfsBlockCache {
getStat(statMiss, key).incrementAndGet();
boolean credit = true;
try {
- v = file.readOneBlock(requestedPosition, ctx,
- fileChannel.get());
+ v = file.readOneBlock(position, ctx, fileChannel.get());
credit = false;
} finally {
if (credit) {
@@ -450,7 +449,7 @@ public final class DfsBlockCache {
}
@SuppressWarnings("unchecked")
- private void reserveSpace(int reserve, DfsStreamKey key) {
+ private void reserveSpace(long reserve, DfsStreamKey key) {
clockLock.lock();
try {
long live = LongStream.of(getCurrentSize()).sum() + reserve;
@@ -487,7 +486,7 @@ public final class DfsBlockCache {
}
}
- private void creditSpace(int credit, DfsStreamKey key) {
+ private void creditSpace(long credit, DfsStreamKey key) {
clockLock.lock();
try {
getStat(liveBytes, key).addAndGet(-credit);
@@ -497,7 +496,7 @@ public final class DfsBlockCache {
}
@SuppressWarnings("unchecked")
- private void addToClock(Ref ref, int credit) {
+ private void addToClock(Ref ref, long credit) {
clockLock.lock();
try {
if (credit != 0) {
@@ -521,17 +520,20 @@ public final class DfsBlockCache {
*
* @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> Ref<T> getOrLoadRef(DfsStreamKey key, RefLoader<T> loader)
+ <T> Ref<T> getOrLoadRef(
+ DfsStreamKey key, long position, RefLoader<T> loader)
throws IOException {
- int slot = slot(key, 0);
+ int slot = slot(key, position);
HashEntry e1 = table.get(slot);
- Ref<T> ref = scanRef(e1, key, 0);
+ Ref<T> ref = scanRef(e1, key, position);
if (ref != null) {
getStat(statHit, key).incrementAndGet();
return ref;
@@ -543,7 +545,7 @@ public final class DfsBlockCache {
try {
HashEntry e2 = table.get(slot);
if (e2 != e1) {
- ref = scanRef(e2, key, 0);
+ ref = scanRef(e2, key, position);
if (ref != null) {
getStat(statHit, key).incrementAndGet();
return ref;
@@ -574,10 +576,10 @@ public final class DfsBlockCache {
}
<T> Ref<T> putRef(DfsStreamKey key, long size, T v) {
- return put(key, 0, (int) Math.min(size, Integer.MAX_VALUE), v);
+ return put(key, 0, size, v);
}
- <T> Ref<T> put(DfsStreamKey key, long pos, int size, T 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);
@@ -720,12 +722,12 @@ public final class DfsBlockCache {
static final class Ref<T> {
final DfsStreamKey key;
final long position;
- final int size;
+ final long size;
volatile T value;
Ref next;
volatile boolean hot;
- Ref(DfsStreamKey key, long position, int size, T v) {
+ Ref(DfsStreamKey key, long position, long size, T v) {
this.key = key;
this.position = position;
this.size = size;
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 3605236e56..9b280747df 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
@@ -61,6 +61,13 @@ public class DfsCachedPack extends CachedPack {
}
/**
+ * @return the pack passed to the constructor
+ */
+ public DfsPackFile getPackFile() {
+ return pack;
+ }
+
+ /**
* Get the description of the pack.
*
* @return the description of the pack.
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 f10a1d8127..3e71d079b5 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
@@ -744,11 +744,15 @@ public class DfsGarbageCollector {
return;
}
- try (ReftableStack stack = ReftableStack.open(ctx, reftablesBefore)) {
- ReftableCompactor compact = new ReftableCompactor();
+ try (DfsReftableStack stack = DfsReftableStack.open(ctx, reftablesBefore);
+ DfsOutputStream out = objdb.writeFile(pack, REFTABLE)) {
+ ReftableCompactor compact = new ReftableCompactor(out);
compact.addAll(stack.readers());
compact.setIncludeDeletes(includeDeletes);
- compactReftable(pack, compact);
+ compact.setConfig(configureReftable(reftableConfig, out));
+ compact.compact();
+ pack.addFileExt(REFTABLE);
+ pack.setReftableStats(compact.getStats());
}
}
@@ -765,24 +769,12 @@ public class DfsGarbageCollector {
throws IOException {
try (DfsOutputStream out = objdb.writeFile(pack, REFTABLE)) {
ReftableConfig cfg = configureReftable(reftableConfig, out);
- ReftableWriter writer = new ReftableWriter(cfg)
+ ReftableWriter writer = new ReftableWriter(cfg, out)
.setMinUpdateIndex(reftableInitialMinUpdateIndex)
- .setMaxUpdateIndex(reftableInitialMaxUpdateIndex)
- .begin(out)
- .sortAndWriteRefs(refs)
- .finish();
+ .setMaxUpdateIndex(reftableInitialMaxUpdateIndex).begin()
+ .sortAndWriteRefs(refs).finish();
pack.addFileExt(REFTABLE);
pack.setReftableStats(writer.getStats());
}
}
-
- private void compactReftable(DfsPackDescription pack,
- ReftableCompactor compact) throws IOException {
- try (DfsOutputStream out = objdb.writeFile(pack, REFTABLE)) {
- compact.setConfig(configureReftable(reftableConfig, out));
- compact.compact(out);
- pack.addFileExt(REFTABLE);
- pack.setReftableStats(compact.getStats());
- }
- }
}
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 09d59376a0..0ee8135faf 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
@@ -97,7 +97,7 @@ public abstract class DfsObjDatabase extends ObjectDatabase {
* comparator based on {@link Enum#compareTo}. Prefer {@link
* #DEFAULT_COMPARATOR} or your own {@link ComparatorBuilder}.
*/
- public static enum PackSource {
+ public enum PackSource {
/** The pack is created by ObjectInserter due to local activity. */
INSERT,
@@ -705,7 +705,7 @@ public abstract class DfsObjDatabase extends ObjectDatabase {
}
/** Snapshot of packs scanned in a single pass. */
- public static abstract class PackList {
+ public abstract static class PackList {
/** All known packs, sorted. */
public final DfsPackFile[] packs;
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 6f3f2bd8e7..083124e5ed 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
@@ -311,12 +311,16 @@ public class DfsPackCompactor {
DfsObjDatabase objdb = repo.getObjectDatabase();
Collections.sort(srcReftables, objdb.reftableComparator());
- try (ReftableStack stack = ReftableStack.open(ctx, srcReftables)) {
- initOutDesc(objdb);
- ReftableCompactor compact = new ReftableCompactor();
+ initOutDesc(objdb);
+ try (DfsReftableStack stack = DfsReftableStack.open(ctx, srcReftables);
+ DfsOutputStream out = objdb.writeFile(outDesc, REFTABLE)) {
+ ReftableCompactor compact = new ReftableCompactor(out);
compact.addAll(stack.readers());
compact.setIncludeDeletes(true);
- writeReftable(objdb, outDesc, compact);
+ compact.setConfig(configureReftable(reftableConfig, out));
+ compact.compact();
+ outDesc.addFileExt(REFTABLE);
+ outDesc.setReftableStats(compact.getStats());
}
}
@@ -497,16 +501,6 @@ public class DfsPackCompactor {
}
}
- private void writeReftable(DfsObjDatabase objdb, DfsPackDescription pack,
- ReftableCompactor compact) throws IOException {
- try (DfsOutputStream out = objdb.writeFile(pack, REFTABLE)) {
- compact.setConfig(configureReftable(reftableConfig, out));
- compact.compact(out);
- pack.addFileExt(REFTABLE);
- pack.setReftableStats(compact.getStats());
- }
- }
-
static ReftableConfig configureReftable(ReftableConfig cfg,
DfsOutputStream out) {
int bs = out.blockSize();
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 be1387ed0c..d0f9b1c1f4 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
@@ -89,6 +89,7 @@ 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;
/**
* Lock for initialization of {@link #index} and {@link #corruptObjects}.
@@ -194,45 +195,10 @@ public final class DfsPackFile extends BlockBasedFile {
try {
DfsStreamKey idxKey = desc.getStreamKey(INDEX);
- DfsBlockCache.Ref<PackIndex> idxref = cache.getOrLoadRef(idxKey,
- () -> {
- try {
- ctx.stats.readIdx++;
- long start = System.nanoTime();
- try (ReadableChannel rc = ctx.db.openFile(desc,
- INDEX)) {
- InputStream in = Channels
- .newInputStream(rc);
- int wantSize = 8192;
- int bs = rc.blockSize();
- if (0 < bs && bs < wantSize) {
- bs = (wantSize / bs) * bs;
- } else if (bs <= 0) {
- bs = wantSize;
- }
- PackIndex idx = PackIndex.read(
- new BufferedInputStream(in, bs));
- int sz = (int) Math.min(
- idx.getObjectCount() * REC_SIZE,
- Integer.MAX_VALUE);
- ctx.stats.readIdxBytes += rc.position();
- index = idx;
- return new DfsBlockCache.Ref<>(idxKey, 0,
- sz, idx);
- } 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);
- }
- });
+ DfsBlockCache.Ref<PackIndex> idxref = cache.getOrLoadRef(
+ idxKey,
+ REF_POSITION,
+ () -> loadPackIndex(ctx, idxKey));
PackIndex idx = idxref.get();
if (index == null && idx != null) {
index = idx;
@@ -267,44 +233,10 @@ public final class DfsPackFile extends BlockBasedFile {
PackIndex idx = idx(ctx);
PackReverseIndex revidx = getReverseIdx(ctx);
DfsStreamKey bitmapKey = desc.getStreamKey(BITMAP_INDEX);
- DfsBlockCache.Ref<PackBitmapIndex> idxref = cache
- .getOrLoadRef(bitmapKey, () -> {
- ctx.stats.readBitmap++;
- long start = System.nanoTime();
- try (ReadableChannel rc = ctx.db.openFile(desc,
- BITMAP_INDEX)) {
- long size;
- PackBitmapIndex bmidx;
- try {
- InputStream in = Channels.newInputStream(rc);
- int wantSize = 8192;
- int bs = rc.blockSize();
- if (0 < bs && bs < wantSize) {
- bs = (wantSize / bs) * bs;
- } else if (bs <= 0) {
- bs = wantSize;
- }
- in = new BufferedInputStream(in, bs);
- bmidx = PackBitmapIndex.read(in, idx, revidx);
- } finally {
- size = rc.position();
- ctx.stats.readIdxBytes += size;
- ctx.stats.readIdxMicros += elapsedMicros(start);
- }
- int sz = (int) Math.min(size, Integer.MAX_VALUE);
- bitmapIndex = bmidx;
- return new DfsBlockCache.Ref<>(bitmapKey, 0, sz,
- 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);
- }
- });
+ DfsBlockCache.Ref<PackBitmapIndex> idxref = cache.getOrLoadRef(
+ bitmapKey,
+ REF_POSITION,
+ () -> loadBitmapIndex(ctx, bitmapKey, idx, revidx));
PackBitmapIndex bmidx = idxref.get();
if (bitmapIndex == null && bmidx != null) {
bitmapIndex = bmidx;
@@ -326,14 +258,10 @@ public final class DfsPackFile extends BlockBasedFile {
PackIndex idx = idx(ctx);
DfsStreamKey revKey = new DfsStreamKey.ForReverseIndex(
desc.getStreamKey(INDEX));
- DfsBlockCache.Ref<PackReverseIndex> revref = cache
- .getOrLoadRef(revKey, () -> {
- PackReverseIndex revidx = new PackReverseIndex(idx);
- int sz = (int) Math.min(idx.getObjectCount() * 8,
- Integer.MAX_VALUE);
- reverseIndex = revidx;
- return new DfsBlockCache.Ref<>(revKey, 0, sz, revidx);
- });
+ DfsBlockCache.Ref<PackReverseIndex> revref = cache.getOrLoadRef(
+ revKey,
+ REF_POSITION,
+ () -> loadReverseIdx(revKey, idx));
PackReverseIndex revidx = revref.get();
if (reverseIndex == null && revidx != null) {
reverseIndex = revidx;
@@ -1091,4 +1019,91 @@ public final class DfsPackFile extends BlockBasedFile {
list.add(offset);
}
}
+
+ 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)) {
+ InputStream in = Channels.newInputStream(rc);
+ int wantSize = 8192;
+ int bs = rc.blockSize();
+ if (0 < bs && bs < wantSize) {
+ bs = (wantSize / bs) * bs;
+ } else if (bs <= 0) {
+ bs = wantSize;
+ }
+ PackIndex idx = PackIndex.read(new BufferedInputStream(in, bs));
+ 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);
+ }
+ } 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);
+ }
+ }
+
+ private DfsBlockCache.Ref<PackReverseIndex> loadReverseIdx(
+ DfsStreamKey revKey, PackIndex idx) {
+ PackReverseIndex revidx = new PackReverseIndex(idx);
+ reverseIndex = revidx;
+ return new DfsBlockCache.Ref<>(
+ revKey,
+ REF_POSITION,
+ idx.getObjectCount() * 8,
+ revidx);
+ }
+
+ private DfsBlockCache.Ref<PackBitmapIndex> loadBitmapIndex(
+ DfsReader ctx,
+ DfsStreamKey bitmapKey,
+ PackIndex idx,
+ PackReverseIndex revidx) throws IOException {
+ ctx.stats.readBitmap++;
+ long start = System.nanoTime();
+ try (ReadableChannel rc = ctx.db.openFile(desc, BITMAP_INDEX)) {
+ long size;
+ PackBitmapIndex bmidx;
+ try {
+ InputStream in = Channels.newInputStream(rc);
+ int wantSize = 8192;
+ int bs = rc.blockSize();
+ if (0 < bs && bs < wantSize) {
+ bs = (wantSize / bs) * bs;
+ } else if (bs <= 0) {
+ bs = wantSize;
+ }
+ in = new BufferedInputStream(in, bs);
+ bmidx = PackBitmapIndex.read(in, idx, revidx);
+ } finally {
+ size = rc.position();
+ ctx.stats.readIdxBytes += size;
+ ctx.stats.readIdxMicros += 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);
+ }
+ }
}
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 732cd4d1c6..b3b9e39375 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
@@ -191,14 +191,11 @@ public abstract class DfsRefDatabase extends RefDatabase {
rw.peel(obj).copy(),
hasVersioning() ? leaf.getUpdateIndex()
: UNDEFINED_UPDATE_INDEX);
- } else {
- return new ObjectIdRef.PeeledNonTag(
- leaf.getStorage(),
- leaf.getName(),
- leaf.getObjectId(),
- hasVersioning() ? leaf.getUpdateIndex()
- : UNDEFINED_UPDATE_INDEX);
}
+ return new ObjectIdRef.PeeledNonTag(leaf.getStorage(),
+ leaf.getName(), leaf.getObjectId(),
+ hasVersioning() ? leaf.getUpdateIndex()
+ : UNDEFINED_UPDATE_INDEX);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableBatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableBatchRefUpdate.java
new file mode 100644
index 0000000000..124630edb7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableBatchRefUpdate.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2019, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.dfs;
+
+import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
+import org.eclipse.jgit.internal.storage.io.BlockSource;
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.internal.storage.reftable.ReftableBatchRefUpdate;
+import org.eclipse.jgit.internal.storage.reftable.ReftableCompactor;
+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.Ref;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
+
+/**
+ * {@link org.eclipse.jgit.lib.BatchRefUpdate} for
+ * {@link org.eclipse.jgit.internal.storage.dfs.DfsReftableDatabase}.
+ */
+public class DfsReftableBatchRefUpdate extends ReftableBatchRefUpdate {
+ private static final int AVG_BYTES = 36;
+
+ private final DfsReftableDatabase refdb;
+
+ private final DfsObjDatabase odb;
+
+ /**
+ * Initialize batch update.
+ *
+ * @param refdb
+ * database the update will modify.
+ * @param odb
+ * object database to store the reftable.
+ */
+ protected DfsReftableBatchRefUpdate(DfsReftableDatabase refdb,
+ DfsObjDatabase odb) {
+ super(refdb, refdb.reftableDatabase, refdb.getLock(), refdb.getRepository());
+ this.refdb = refdb;
+ this.odb = odb;
+ }
+
+ @Override
+ protected void applyUpdates(List<Ref> newRefs, List<ReceiveCommand> pending)
+ throws IOException {
+ Set<DfsPackDescription> prune = Collections.emptySet();
+ DfsPackDescription pack = odb.newPack(PackSource.INSERT);
+ try (DfsOutputStream out = odb.writeFile(pack, REFTABLE)) {
+ ReftableConfig cfg = DfsPackCompactor
+ .configureReftable(refdb.getReftableConfig(), out);
+
+ ReftableWriter.Stats stats;
+ if (refdb.compactDuringCommit()
+ && newRefs.size() * AVG_BYTES <= cfg.getRefBlockSize()
+ && canCompactTopOfStack(cfg)) {
+ ByteArrayOutputStream tmp = new ByteArrayOutputStream();
+ ReftableWriter rw = new ReftableWriter(cfg, tmp);
+ write(rw, newRefs, pending);
+ rw.finish();
+ stats = compactTopOfStack(out, cfg, tmp.toByteArray());
+ prune = toPruneTopOfStack();
+ } else {
+ ReftableWriter rw = new ReftableWriter(cfg, out);
+ write(rw, newRefs, pending);
+ rw.finish();
+ stats = rw.getStats();
+ }
+ pack.addFileExt(REFTABLE);
+ pack.setReftableStats(stats);
+ }
+
+ odb.commitPack(Collections.singleton(pack), prune);
+ odb.addReftable(pack, prune);
+ refdb.clearCache();
+ }
+
+ private boolean canCompactTopOfStack(ReftableConfig cfg)
+ throws IOException {
+ refdb.getLock().lock();
+ try {
+ DfsReftableStack stack = refdb.stack();
+ List<ReftableReader> readers = stack.readers();
+ if (readers.isEmpty()) {
+ return false;
+ }
+
+ int lastIdx = readers.size() - 1;
+ DfsReftable last = stack.files().get(lastIdx);
+ DfsPackDescription desc = last.getPackDescription();
+ if (desc.getPackSource() != PackSource.INSERT
+ || !packOnlyContainsReftable(desc)) {
+ return false;
+ }
+
+ ReftableReader table = readers.get(lastIdx);
+ int bs = cfg.getRefBlockSize();
+ return table.size() <= 3 * bs;
+ } finally {
+ refdb.getLock().unlock();
+ }
+ }
+
+ private ReftableWriter.Stats compactTopOfStack(OutputStream out,
+ ReftableConfig cfg, byte[] newTable) throws IOException {
+ refdb.getLock().lock();
+ try {
+ List<ReftableReader> stack = refdb.stack().readers();
+
+ ReftableReader last = stack.get(stack.size() - 1);
+
+ List<ReftableReader> tables = new ArrayList<>(2);
+ tables.add(last);
+ tables.add(new ReftableReader(BlockSource.from(newTable)));
+
+ ReftableCompactor compactor = new ReftableCompactor(out);
+ compactor.setConfig(cfg);
+ compactor.setIncludeDeletes(true);
+ compactor.addAll(tables);
+ compactor.compact();
+ return compactor.getStats();
+ } finally {
+ refdb.getLock().unlock();
+ }
+ }
+
+ private Set<DfsPackDescription> toPruneTopOfStack() throws IOException {
+ refdb.getLock().lock();
+ try {
+ List<DfsReftable> stack = refdb.stack().files();
+
+ DfsReftable last = stack.get(stack.size() - 1);
+ return Collections.singleton(last.getPackDescription());
+ } finally {
+ refdb.getLock().unlock();
+ }
+ }
+
+ private boolean packOnlyContainsReftable(DfsPackDescription desc) {
+ for (PackExt ext : PackExt.values()) {
+ if (ext != REFTABLE && desc.hasFileExt(ext)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
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 6050c15992..124131d1d3 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
@@ -45,19 +45,17 @@ package org.eclipse.jgit.internal.storage.dfs;
import java.io.IOException;
import java.util.Arrays;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeSet;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.internal.storage.reftable.MergedReftable;
-import org.eclipse.jgit.internal.storage.reftable.RefCursor;
-import org.eclipse.jgit.internal.storage.reftable.Reftable;
import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
+import org.eclipse.jgit.internal.storage.reftable.ReftableDatabase;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
@@ -81,13 +79,10 @@ import org.eclipse.jgit.util.RefMap;
* and one will fail.
*/
public class DfsReftableDatabase extends DfsRefDatabase {
- private final ReentrantLock lock = new ReentrantLock(true);
+ final ReftableDatabase reftableDatabase;
private DfsReader ctx;
-
- private ReftableStack tableStack;
-
- private MergedReftable mergedTables;
+ private DfsReftableStack stack;
/**
* Initialize the reference database for a repository.
@@ -97,6 +92,18 @@ public class DfsReftableDatabase extends DfsRefDatabase {
*/
protected DfsReftableDatabase(DfsRepository repo) {
super(repo);
+ reftableDatabase = new ReftableDatabase() {
+ @Override
+ public MergedReftable openMergedReftable() throws IOException {
+ DfsReftableDatabase.this.getLock().lock();
+ try {
+ return new MergedReftable(stack().readers());
+ } finally {
+ DfsReftableDatabase.this.getLock().unlock();
+ }
+ }
+ };
+ stack = null;
}
/** {@inheritDoc} */
@@ -115,7 +122,7 @@ public class DfsReftableDatabase extends DfsRefDatabase {
@Override
public BatchRefUpdate newBatchUpdate() {
DfsObjDatabase odb = getRepository().getObjectDatabase();
- return new ReftableBatchRefUpdate(this, odb);
+ return new DfsReftableBatchRefUpdate(this, odb);
}
/**
@@ -124,7 +131,7 @@ public class DfsReftableDatabase extends DfsRefDatabase {
* @return configuration to write new reftables with.
*/
public ReftableConfig getReftableConfig() {
- return new ReftableConfig(getRepository().getConfig());
+ return new ReftableConfig(getRepository());
}
/**
@@ -133,7 +140,7 @@ public class DfsReftableDatabase extends DfsRefDatabase {
* @return the lock protecting this instance's state.
*/
protected ReentrantLock getLock() {
- return lock;
+ return reftableDatabase.getLock();
}
/**
@@ -147,134 +154,57 @@ public class DfsReftableDatabase extends DfsRefDatabase {
return true;
}
- /**
- * Obtain a handle to the merged reader.
- *
- * @return (possibly cached) handle to the merged reader.
- * @throws java.io.IOException
- * if tables cannot be opened.
- */
- protected Reftable reader() throws IOException {
- lock.lock();
- try {
- if (mergedTables == null) {
- mergedTables = new MergedReftable(stack().readers());
- }
- return mergedTables;
- } finally {
- lock.unlock();
- }
- }
/**
- * Obtain a handle to the stack of reftables.
+ * Obtain a handle to the stack of reftables. Must hold lock.
*
* @return (possibly cached) handle to the stack.
* @throws java.io.IOException
* if tables cannot be opened.
*/
- protected ReftableStack stack() throws IOException {
- lock.lock();
- try {
- if (tableStack == null) {
- DfsObjDatabase odb = getRepository().getObjectDatabase();
- if (ctx == null) {
- ctx = odb.newReader();
- }
- tableStack = ReftableStack.open(ctx,
- Arrays.asList(odb.getReftables()));
- }
- return tableStack;
- } finally {
- lock.unlock();
+ protected DfsReftableStack stack() throws IOException {
+ if (!getLock().isLocked()) {
+ throw new IllegalStateException("most hold lock to access stack"); //$NON-NLS-1$
}
+ DfsObjDatabase odb = getRepository().getObjectDatabase();
+
+ if (ctx == null) {
+ ctx = odb.newReader();
+ }
+ if (stack == null) {
+ stack = DfsReftableStack.open(ctx, Arrays.asList(odb.getReftables()));
+ }
+ return stack;
}
- /** {@inheritDoc} */
@Override
public boolean isNameConflicting(String refName) throws IOException {
- lock.lock();
- try {
- Reftable table = reader();
-
- // Cannot be nested within an existing reference.
- int lastSlash = refName.lastIndexOf('/');
- while (0 < lastSlash) {
- if (table.hasRef(refName.substring(0, lastSlash))) {
- return true;
- }
- lastSlash = refName.lastIndexOf('/', lastSlash - 1);
- }
-
- // Cannot be the container of an existing reference.
- return table.hasRefsWithPrefix(refName + '/');
- } finally {
- lock.unlock();
- }
+ return reftableDatabase.isNameConflicting(refName, new TreeSet<>(), new HashSet<>());
}
/** {@inheritDoc} */
@Override
public Ref exactRef(String name) throws IOException {
- lock.lock();
- try {
- Reftable table = reader();
- Ref ref = table.exactRef(name);
- if (ref != null && ref.isSymbolic()) {
- return table.resolve(ref);
- }
- return ref;
- } finally {
- lock.unlock();
- }
+ return reftableDatabase.exactRef(name);
}
/** {@inheritDoc} */
@Override
public Map<String, Ref> getRefs(String prefix) throws IOException {
- RefList.Builder<Ref> all = new RefList.Builder<>();
- lock.lock();
- try {
- Reftable table = reader();
- try (RefCursor rc = ALL.equals(prefix) ? table.allRefs()
- : (prefix.endsWith("/") ? table.seekRefsWithPrefix(prefix) //$NON-NLS-1$
- : table.seekRef(prefix))) {
- while (rc.next()) {
- Ref ref = table.resolve(rc.getRef());
- if (ref != null && ref.getObjectId() != null) {
- all.add(ref);
- }
- }
- }
- } finally {
- lock.unlock();
+ List<Ref> refs = reftableDatabase.getRefsByPrefix(prefix);
+ RefList.Builder<Ref> builder = new RefList.Builder<>(refs.size());
+ for (Ref r : refs) {
+ builder.add(r);
}
-
- RefList<Ref> none = RefList.emptyList();
- return new RefMap(prefix, all.toRefList(), none, none);
+ return new RefMap(prefix, builder.toRefList(), RefList.emptyList(),
+ RefList.emptyList());
}
/** {@inheritDoc} */
@Override
public List<Ref> getRefsByPrefix(String prefix) throws IOException {
- List<Ref> all = new ArrayList<>();
- lock.lock();
- try {
- Reftable table = reader();
- try (RefCursor rc = ALL.equals(prefix) ? table.allRefs()
- : table.seekRefsWithPrefix(prefix)) {
- while (rc.next()) {
- Ref ref = table.resolve(rc.getRef());
- if (ref != null && ref.getObjectId() != null) {
- all.add(ref);
- }
- }
- }
- } finally {
- lock.unlock();
- }
- return Collections.unmodifiableList(all);
+ return reftableDatabase.getRefsByPrefix(prefix);
}
/** {@inheritDoc} */
@@ -283,17 +213,13 @@ public class DfsReftableDatabase extends DfsRefDatabase {
if (!getReftableConfig().isIndexObjects()) {
return super.getTipsWithSha1(id);
}
- lock.lock();
- try {
- RefCursor cursor = reader().byObjectId(id);
- Set<Ref> refs = new HashSet<>();
- while (cursor.next()) {
- refs.add(cursor.getRef());
- }
- return refs;
- } finally {
- lock.unlock();
- }
+ return reftableDatabase.getTipsWithSha1(id);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean hasFastTipsWithSha1() throws IOException {
+ return reftableDatabase.hasFastTipsWithSha1();
}
/** {@inheritDoc} */
@@ -314,19 +240,19 @@ public class DfsReftableDatabase extends DfsRefDatabase {
@Override
void clearCache() {
- lock.lock();
+ getLock().lock();
try {
- if (tableStack != null) {
- tableStack.close();
- tableStack = null;
- }
if (ctx != null) {
ctx.close();
ctx = null;
}
- mergedTables = null;
+ reftableDatabase.clearCache();
+ if (stack != null) {
+ stack.close();
+ stack = null;
+ }
} finally {
- lock.unlock();
+ getLock().unlock();
}
}
@@ -334,7 +260,7 @@ public class DfsReftableDatabase extends DfsRefDatabase {
@Override
protected boolean compareAndPut(Ref oldRef, @Nullable Ref newRef)
throws IOException {
- ReceiveCommand cmd = toCommand(oldRef, newRef);
+ ReceiveCommand cmd = ReftableDatabase.toCommand(oldRef, newRef);
try (RevWalk rw = new RevWalk(getRepository())) {
rw.setRetainBody(false);
newBatchUpdate().setAllowNonFastForwards(true).addCommand(cmd)
@@ -351,58 +277,6 @@ public class DfsReftableDatabase extends DfsRefDatabase {
}
}
- private static ReceiveCommand toCommand(Ref oldRef, Ref newRef) {
- ObjectId oldId = toId(oldRef);
- ObjectId newId = toId(newRef);
- String name = toName(oldRef, newRef);
-
- if (oldRef != null && oldRef.isSymbolic()) {
- if (newRef != null) {
- if (newRef.isSymbolic()) {
- return ReceiveCommand.link(oldRef.getTarget().getName(),
- newRef.getTarget().getName(), name);
- } else {
- return ReceiveCommand.unlink(oldRef.getTarget().getName(),
- newId, name);
- }
- } else {
- return ReceiveCommand.unlink(oldRef.getTarget().getName(),
- ObjectId.zeroId(), name);
- }
- }
-
- if (newRef != null && newRef.isSymbolic()) {
- if (oldRef != null) {
- if (oldRef.isSymbolic()) {
- return ReceiveCommand.link(oldRef.getTarget().getName(),
- newRef.getTarget().getName(), name);
- } else {
- return ReceiveCommand.link(oldId,
- newRef.getTarget().getName(), name);
- }
- } else {
- return ReceiveCommand.link(ObjectId.zeroId(),
- newRef.getTarget().getName(), name);
- }
- }
-
- return new ReceiveCommand(oldId, newId, name);
- }
-
- private static ObjectId toId(Ref ref) {
- if (ref != null) {
- ObjectId id = ref.getObjectId();
- if (id != null) {
- return id;
- }
- }
- return ObjectId.zeroId();
- }
-
- private static String toName(Ref oldRef, Ref newRef) {
- return oldRef != null ? oldRef.getName() : newRef.getName();
- }
-
/** {@inheritDoc} */
@Override
protected boolean compareAndRemove(Ref oldRef) throws IOException {
@@ -417,12 +291,12 @@ public class DfsReftableDatabase extends DfsRefDatabase {
@Override
void stored(Ref ref) {
- // Unnecessary; ReftableBatchRefUpdate calls clearCache().
+ // Unnecessary; DfsReftableBatchRefUpdate calls clearCache().
}
@Override
void removed(String refName) {
- // Unnecessary; ReftableBatchRefUpdate calls clearCache().
+ // Unnecessary; DfsReftableBatchRefUpdate calls clearCache().
}
/** {@inheritDoc} */
@@ -430,4 +304,5 @@ public class DfsReftableDatabase extends DfsRefDatabase {
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/ReftableStack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableStack.java
index 50ba0e0f38..59621a4e67 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableStack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReftableStack.java
@@ -48,13 +48,13 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import org.eclipse.jgit.internal.storage.reftable.Reftable;
+import org.eclipse.jgit.internal.storage.reftable.ReftableReader;
/**
* Tracks multiple open
- * {@link org.eclipse.jgit.internal.storage.reftable.Reftable} instances.
+ * {@link org.eclipse.jgit.internal.storage.reftable.ReftableReader} instances.
*/
-public class ReftableStack implements AutoCloseable {
+public class DfsReftableStack implements AutoCloseable {
/**
* Opens a stack of tables for reading.
*
@@ -67,9 +67,9 @@ public class ReftableStack implements AutoCloseable {
* @throws java.io.IOException
* a table could not be opened
*/
- public static ReftableStack open(DfsReader ctx, List<DfsReftable> files)
+ public static DfsReftableStack open(DfsReader ctx, List<DfsReftable> files)
throws IOException {
- ReftableStack stack = new ReftableStack(files.size());
+ DfsReftableStack stack = new DfsReftableStack(files.size());
boolean close = true;
try {
for (DfsReftable t : files) {
@@ -86,9 +86,9 @@ public class ReftableStack implements AutoCloseable {
}
private final List<DfsReftable> files;
- private final List<Reftable> tables;
+ private final List<ReftableReader> tables;
- private ReftableStack(int tableCnt) {
+ private DfsReftableStack(int tableCnt) {
this.files = new ArrayList<>(tableCnt);
this.tables = new ArrayList<>(tableCnt);
}
@@ -109,14 +109,14 @@ public class ReftableStack implements AutoCloseable {
* @return unmodifiable list of tables, in the same order the files were
* passed to {@link #open(DfsReader, List)}.
*/
- public List<Reftable> readers() {
+ public List<ReftableReader> readers() {
return Collections.unmodifiableList(tables);
}
/** {@inheritDoc} */
@Override
public void close() {
- for (Reftable t : tables) {
+ for (ReftableReader t : tables) {
try {
t.close();
} catch (IOException e) {
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 1f6b20aacf..db5b1279be 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
@@ -60,7 +60,7 @@ import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.util.FS;
abstract class FileObjectDatabase extends ObjectDatabase {
- static enum InsertLooseObjectResult {
+ enum InsertLooseObjectResult {
INSERTED, EXISTS_PACKED, EXISTS_LOOSE, FAILURE;
}
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
new file mode 100644
index 0000000000..e5ffd77c5d
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableDatabase.java
@@ -0,0 +1,669 @@
+/*
+ * Copyright (C) 2019 Google LLC
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.file;
+
+import static org.eclipse.jgit.lib.Ref.UNDEFINED_UPDATE_INDEX;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.events.RefsChangedEvent;
+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.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefRename;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.lib.ReflogReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.RefList;
+import org.eclipse.jgit.util.RefMap;
+
+/**
+ * Implements RefDatabase using reftable for storage.
+ *
+ * This class is threadsafe.
+ */
+public class FileReftableDatabase extends RefDatabase {
+ private final ReftableDatabase reftableDatabase;
+
+ private final FileRepository fileRepository;
+
+ private final FileReftableStack reftableStack;
+
+ FileReftableDatabase(FileRepository repo) throws IOException {
+ this(repo, new File(new File(repo.getDirectory(), Constants.REFTABLE),
+ Constants.TABLES_LIST));
+ }
+
+ FileReftableDatabase(FileRepository repo, File refstackName) throws IOException {
+ this.fileRepository = repo;
+ this.reftableStack = new FileReftableStack(refstackName,
+ new File(fileRepository.getDirectory(), Constants.REFTABLE),
+ () -> fileRepository.fireEvent(new RefsChangedEvent()),
+ () -> fileRepository.getConfig());
+ this.reftableDatabase = new ReftableDatabase() {
+
+ @Override
+ public MergedReftable openMergedReftable() throws IOException {
+ return reftableStack.getMergedReftable();
+ }
+ };
+ }
+
+ ReflogReader getReflogReader(String refname) throws IOException {
+ return reftableDatabase.getReflogReader(refname);
+ }
+
+ /**
+ * @param repoDir
+ * @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();
+ }
+
+ /**
+ * Runs a full compaction for GC purposes.
+ * @throws IOException on I/O errors
+ */
+ public void compactFully() throws IOException {
+ reftableDatabase.getLock().lock();
+ try {
+ reftableStack.compactFully();
+ reftableDatabase.clearCache();
+ } finally {
+ reftableDatabase.getLock().unlock();
+ }
+ }
+
+ private ReentrantLock getLock() {
+ 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 {
+ boolean detachingSymbolicRef = false;
+ Ref ref = exactRef(refName);
+
+ if (ref == null) {
+ ref = new ObjectIdRef.Unpeeled(NEW, refName, null);
+ } else {
+ detachingSymbolicRef = detach && ref.isSymbolic();
+ }
+
+ RefUpdate update = new FileReftableRefUpdate(ref);
+ if (detachingSymbolicRef) {
+ update.setDetachingSymbolicRef();
+ }
+ return update;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Ref exactRef(String name) throws IOException {
+ 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 {
+ List<Ref> refs = reftableDatabase.getRefsByPrefix(prefix);
+ RefList.Builder<Ref> builder = new RefList.Builder<>(refs.size());
+ for (Ref r : refs) {
+ builder.add(r);
+ }
+ return new RefMap(prefix, builder.toRefList(), RefList.emptyList(),
+ RefList.emptyList());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public List<Ref> getAdditionalRefs() throws IOException {
+ return Collections.emptyList();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Ref peel(Ref ref) throws IOException {
+ Ref oldLeaf = ref.getLeaf();
+ if (oldLeaf.isPeeled() || oldLeaf.getObjectId() == null) {
+ return ref;
+ }
+ return recreate(ref, doPeel(oldLeaf), hasVersioning());
+
+ }
+
+ private Ref doPeel(Ref leaf) throws IOException {
+ try (RevWalk rw = new RevWalk(fileRepository)) {
+ RevObject obj = rw.parseAny(leaf.getObjectId());
+ if (obj instanceof RevTag) {
+ return new ObjectIdRef.PeeledTag(leaf.getStorage(),
+ leaf.getName(), leaf.getObjectId(), rw.peel(obj).copy(),
+ hasVersioning() ? leaf.getUpdateIndex()
+ : UNDEFINED_UPDATE_INDEX);
+ }
+ return new ObjectIdRef.PeeledNonTag(leaf.getStorage(),
+ leaf.getName(), leaf.getObjectId(),
+ hasVersioning() ? leaf.getUpdateIndex()
+ : UNDEFINED_UPDATE_INDEX);
+
+ }
+ }
+
+ private static Ref recreate(Ref old, Ref leaf, boolean hasVersioning) {
+ if (old.isSymbolic()) {
+ Ref dst = recreate(old.getTarget(), leaf, hasVersioning);
+ return new SymbolicRef(old.getName(), dst,
+ hasVersioning ? old.getUpdateIndex()
+ : UNDEFINED_UPDATE_INDEX);
+ }
+ return leaf;
+ }
+
+ private class FileRefRename extends RefRename {
+ FileRefRename(RefUpdate src, RefUpdate dst) {
+ super(src, dst);
+ }
+
+ void writeRename(ReftableWriter w) throws IOException {
+ long idx = reftableDatabase.nextUpdateIndex();
+ w.setMinUpdateIndex(idx).setMaxUpdateIndex(idx).begin();
+ List<Ref> refs = new ArrayList<>(3);
+
+ Ref dest = destination.getRef();
+ Ref head = exactRef(Constants.HEAD);
+ if (head != null && head.isSymbolic()
+ && head.getLeaf().getName().equals(source.getName())) {
+ head = new SymbolicRef(Constants.HEAD, dest, idx);
+ refs.add(head);
+ }
+
+ ObjectId objId = source.getRef().getObjectId();
+
+ // XXX should we check if the source is a Tag vs. NonTag?
+ refs.add(new ObjectIdRef.PeeledNonTag(Ref.Storage.NEW,
+ destination.getName(), objId));
+ refs.add(new ObjectIdRef.Unpeeled(Ref.Storage.NEW, source.getName(),
+ null));
+
+ w.sortAndWriteRefs(refs);
+ PersonIdent who = destination.getRefLogIdent();
+ if (who == null) {
+ who = new PersonIdent(fileRepository);
+ }
+
+ if (!destination.getRefLogMessage().isEmpty()) {
+ List<String> refnames = refs.stream().map(r -> r.getName())
+ .collect(Collectors.toList());
+ Collections.sort(refnames);
+ for (String s : refnames) {
+ ObjectId old = (Constants.HEAD.equals(s)
+ || s.equals(source.getName())) ? objId
+ : ObjectId.zeroId();
+ ObjectId newId = (Constants.HEAD.equals(s)
+ || s.equals(destination.getName())) ? objId
+ : ObjectId.zeroId();
+
+ w.writeLog(s, idx, who, old, newId,
+ destination.getRefLogMessage());
+ }
+ }
+ }
+
+ @Override
+ protected RefUpdate.Result doRename() throws IOException {
+ Ref src = exactRef(source.getName());
+ if (exactRef(destination.getName()) != null || src == null
+ || !source.getOldObjectId().equals(src.getObjectId())) {
+ return RefUpdate.Result.LOCK_FAILURE;
+ }
+
+ if (src.isSymbolic()) {
+ // We could support this, but this is easier and compatible.
+ return RefUpdate.Result.IO_FAILURE;
+ }
+
+ if (!addReftable(this::writeRename)) {
+ return RefUpdate.Result.LOCK_FAILURE;
+ }
+
+ return RefUpdate.Result.RENAMED;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public RefRename newRename(String fromName, String toName)
+ throws IOException {
+ RefUpdate src = newUpdate(fromName, true);
+ RefUpdate dst = newUpdate(toName, true);
+ 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),
+ true);
+ }
+
+ private boolean addReftable(FileReftableStack.Writer w) throws IOException {
+ if (!reftableStack.addReftable(w)) {
+ reftableStack.reload();
+ reftableDatabase.clearCache();
+ return false;
+ }
+ reftableDatabase.clearCache();
+
+ return true;
+ }
+
+ private class FileReftableBatchRefUpdate extends ReftableBatchRefUpdate {
+ FileReftableBatchRefUpdate(FileReftableDatabase db,
+ Repository repository) {
+ super(db, db.reftableDatabase, db.getLock(), repository);
+ }
+
+ @Override
+ protected void applyUpdates(List<Ref> newRefs,
+ List<ReceiveCommand> pending) throws IOException {
+ if (!addReftable(rw -> write(rw, newRefs, pending))) {
+ for (ReceiveCommand c : pending) {
+ if (c.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
+ c.setResult(RefUpdate.Result.LOCK_FAILURE);
+ }
+ }
+ }
+ }
+ }
+
+ private class FileReftableRefUpdate extends RefUpdate {
+ FileReftableRefUpdate(Ref ref) {
+ super(ref);
+ }
+
+ @Override
+ protected RefDatabase getRefDatabase() {
+ return FileReftableDatabase.this;
+ }
+
+ @Override
+ protected Repository getRepository() {
+ return FileReftableDatabase.this.fileRepository;
+ }
+
+ @Override
+ protected void unlock() {
+ // nop.
+ }
+
+ private RevWalk rw;
+
+ private Ref dstRef;
+
+ @Override
+ public Result update(RevWalk walk) throws IOException {
+ try {
+ rw = walk;
+ return super.update(walk);
+ } finally {
+ rw = null;
+ }
+ }
+
+ @Override
+ protected boolean tryLock(boolean deref) throws IOException {
+ dstRef = getRef();
+ if (deref) {
+ dstRef = dstRef.getLeaf();
+ }
+
+ Ref derefed = exactRef(dstRef.getName());
+ if (derefed != null) {
+ setOldObjectId(derefed.getObjectId());
+ }
+
+ return true;
+ }
+
+ void writeUpdate(ReftableWriter w) throws IOException {
+ Ref newRef = null;
+ if (rw != null && !ObjectId.zeroId().equals(getNewObjectId())) {
+ RevObject obj = rw.parseAny(getNewObjectId());
+ if (obj instanceof RevTag) {
+ newRef = new ObjectIdRef.PeeledTag(Ref.Storage.PACKED,
+ dstRef.getName(), getNewObjectId(),
+ rw.peel(obj).copy());
+ }
+ }
+ if (newRef == null) {
+ newRef = new ObjectIdRef.PeeledNonTag(Ref.Storage.PACKED,
+ dstRef.getName(), getNewObjectId());
+ }
+
+ long idx = reftableDatabase.nextUpdateIndex();
+ w.setMinUpdateIndex(idx).setMaxUpdateIndex(idx).begin()
+ .writeRef(newRef);
+
+ ObjectId oldId = getOldObjectId();
+ if (oldId == null) {
+ oldId = ObjectId.zeroId();
+ }
+ w.writeLog(dstRef.getName(), idx, getRefLogIdent(), oldId,
+ getNewObjectId(), getRefLogMessage());
+ }
+
+ @Override
+ public PersonIdent getRefLogIdent() {
+ PersonIdent who = super.getRefLogIdent();
+ if (who == null) {
+ who = new PersonIdent(getRepository());
+ }
+ return who;
+ }
+
+ void writeDelete(ReftableWriter w) throws IOException {
+ Ref newRef = new ObjectIdRef.Unpeeled(Ref.Storage.NEW,
+ dstRef.getName(), null);
+ long idx = reftableDatabase.nextUpdateIndex();
+ w.setMinUpdateIndex(idx).setMaxUpdateIndex(idx).begin()
+ .writeRef(newRef);
+
+ ObjectId oldId = ObjectId.zeroId();
+ Ref old = exactRef(dstRef.getName());
+ if (old != null) {
+ old = old.getLeaf();
+ if (old.getObjectId() != null) {
+ oldId = old.getObjectId();
+ }
+ }
+
+ w.writeLog(dstRef.getName(), idx, getRefLogIdent(), oldId,
+ ObjectId.zeroId(), getRefLogMessage());
+ }
+
+ @Override
+ protected Result doUpdate(Result desiredResult) throws IOException {
+ if (isRefLogIncludingResult()) {
+ setRefLogMessage(
+ getRefLogMessage() + ": " + desiredResult.toString(), //$NON-NLS-1$
+ false);
+ }
+
+ if (!addReftable(this::writeUpdate)) {
+ return Result.LOCK_FAILURE;
+ }
+
+ return desiredResult;
+ }
+
+ @Override
+ protected Result doDelete(Result desiredResult) throws IOException {
+
+ if (isRefLogIncludingResult()) {
+ setRefLogMessage(
+ getRefLogMessage() + ": " + desiredResult.toString(), //$NON-NLS-1$
+ false);
+ }
+
+ if (!addReftable(this::writeDelete)) {
+ return Result.LOCK_FAILURE;
+ }
+
+ return desiredResult;
+ }
+
+ void writeLink(ReftableWriter w) throws IOException {
+ long idx = reftableDatabase.nextUpdateIndex();
+ w.setMinUpdateIndex(idx).setMaxUpdateIndex(idx).begin()
+ .writeRef(dstRef);
+
+ ObjectId beforeId = ObjectId.zeroId();
+ Ref before = exactRef(dstRef.getName());
+ if (before != null) {
+ before = before.getLeaf();
+ if (before.getObjectId() != null) {
+ beforeId = before.getObjectId();
+ }
+ }
+
+ Ref after = dstRef.getLeaf();
+ ObjectId afterId = ObjectId.zeroId();
+ if (after.getObjectId() != null) {
+ afterId = after.getObjectId();
+ }
+
+ w.writeLog(dstRef.getName(), idx, getRefLogIdent(), beforeId,
+ afterId, getRefLogMessage());
+ }
+
+ @Override
+ protected Result doLink(String target) throws IOException {
+ if (isRefLogIncludingResult()) {
+ setRefLogMessage(
+ getRefLogMessage() + ": " + Result.FORCED.toString(), //$NON-NLS-1$
+ false);
+ }
+
+ boolean exists = exactRef(getName()) != null;
+ dstRef = new SymbolicRef(getName(),
+ new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null),
+ reftableDatabase.nextUpdateIndex());
+
+ if (!addReftable(this::writeLink)) {
+ return Result.LOCK_FAILURE;
+ }
+ // XXX unclear if we should support FORCED here. Baseclass says
+ // NEW is OK ?
+ return exists ? Result.FORCED : Result.NEW;
+ }
+ }
+
+ private static void writeConvertTable(Repository repo, ReftableWriter w,
+ boolean writeLogs) throws IOException {
+ int size = 0;
+ List<Ref> refs = repo.getRefDatabase().getRefs();
+ if (writeLogs) {
+ for (Ref r : refs) {
+ ReflogReader rlr = repo.getReflogReader(r.getName());
+ if (rlr != null) {
+ size = Math.max(rlr.getReverseEntries().size(), size);
+ }
+ }
+ }
+ // We must use 1 here, nextUpdateIndex() on the empty stack is 1.
+ w.setMinUpdateIndex(1).setMaxUpdateIndex(size + 1).begin();
+
+ // The spec says to write the logs in the first table, and put refs in a
+ // separate table, but this complicates the compaction (when we can we drop
+ // deletions? Can we compact the .log table and the .ref table together?)
+ try (RevWalk rw = new RevWalk(repo)) {
+ List<Ref> toWrite = new ArrayList<>(refs.size());
+ for (Ref r : refs) {
+ toWrite.add(refForWrite(rw, r));
+ }
+ w.sortAndWriteRefs(toWrite);
+ }
+
+ if (writeLogs) {
+ for (Ref r : refs) {
+ long idx = size;
+ ReflogReader reader = repo.getReflogReader(r.getName());
+ if (reader == null) {
+ continue;
+ }
+ for (ReflogEntry e : reader.getReverseEntries()) {
+ w.writeLog(r.getName(), idx, e.getWho(), e.getOldId(),
+ e.getNewId(), e.getComment());
+ idx--;
+ }
+ }
+ }
+ }
+
+ private static Ref refForWrite(RevWalk rw, Ref r) throws IOException {
+ if (r.isSymbolic()) {
+ return new SymbolicRef(r.getName(), new ObjectIdRef.Unpeeled(NEW,
+ r.getTarget().getName(), null));
+ }
+ ObjectId newId = r.getObjectId();
+ RevObject peel = null;
+ try {
+ RevObject obj = rw.parseAny(newId);
+ if (obj instanceof RevTag) {
+ peel = rw.peel(obj);
+ }
+ } catch (MissingObjectException e) {
+ /* ignore this error and copy the dangling object ID into reftable too. */
+ }
+ if (peel != null) {
+ return new ObjectIdRef.PeeledTag(PACKED, r.getName(), newId,
+ peel.copy());
+ }
+
+ return new ObjectIdRef.PeeledNonTag(PACKED, r.getName(), newId);
+ }
+
+ /**
+ * @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();
+ }
+
+ 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();
+ }
+ }
+ 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
new file mode 100644
index 0000000000..68d82c20d2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileReftableStack.java
@@ -0,0 +1,766 @@
+/*
+ * Copyright (C) 2019 Google LLC
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.file;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.LockFailedException;
+import org.eclipse.jgit.internal.storage.io.BlockSource;
+import org.eclipse.jgit.internal.storage.reftable.MergedReftable;
+import org.eclipse.jgit.internal.storage.reftable.ReftableCompactor;
+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.util.FileUtils;
+
+/**
+ * A mutable stack of reftables on local filesystem storage. Not thread-safe.
+ * This is an AutoCloseable because this object owns the file handles to the
+ * open reftables.
+ */
+public class FileReftableStack implements AutoCloseable {
+ private static class StackEntry {
+
+ String name;
+
+ ReftableReader reftableReader;
+ }
+
+ private MergedReftable mergedReftable;
+
+ private List<StackEntry> stack;
+
+ private long lastNextUpdateIndex;
+
+ private final File stackPath;
+
+ private final File reftableDir;
+
+ private final Runnable onChange;
+
+ private final Supplier<Config> configSupplier;
+
+ // Used for stats & testing.
+ static class CompactionStats {
+
+ long tables;
+
+ long bytes;
+
+ int attempted;
+
+ int failed;
+
+ long refCount;
+
+ long logCount;
+
+ CompactionStats() {
+ tables = 0;
+ bytes = 0;
+ attempted = 0;
+ failed = 0;
+ logCount = 0;
+ refCount = 0;
+ }
+ }
+
+ private final CompactionStats stats;
+
+ /**
+ * 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
+ * hook to call if we notice a new write
+ * @param configSupplier
+ * Config supplier
+ * @throws IOException
+ * on I/O problems
+ */
+ public FileReftableStack(File stackPath, File reftableDir,
+ @Nullable Runnable onChange, Supplier<Config> configSupplier)
+ throws IOException {
+ this.stackPath = stackPath;
+ this.reftableDir = reftableDir;
+ this.stack = new ArrayList<>();
+ this.configSupplier = configSupplier;
+ this.onChange = onChange;
+
+ // skip event notification
+ lastNextUpdateIndex = 0;
+ reload();
+
+ stats = new CompactionStats();
+ }
+
+ CompactionStats getStats() {
+ return stats;
+ }
+
+ /** Thrown if the update indices in the stack are not monotonic */
+ public static class ReftableNumbersNotIncreasingException
+ extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ String name;
+
+ long lastMax;
+
+ long min;
+
+ ReftableNumbersNotIncreasingException(String name, long lastMax,
+ long min) {
+ this.name = name;
+ this.lastMax = lastMax;
+ this.min = min;
+ }
+ }
+
+ /**
+ * Reloads the stack, potentially reusing opened reftableReaders.
+ *
+ * @param names
+ * holds the names of the tables to load.
+ * @throws FileNotFoundException
+ * load must be retried.
+ * @throws IOException
+ * on other IO errors.
+ */
+ private void reloadOnce(List<String> names)
+ throws IOException, FileNotFoundException {
+ Map<String, ReftableReader> current = stack.stream()
+ .collect(Collectors.toMap(e -> e.name, e -> e.reftableReader));
+
+ List<ReftableReader> newTables = new ArrayList<>();
+ List<StackEntry> newStack = new ArrayList<>(stack.size() + 1);
+ try {
+ ReftableReader last = null;
+ for (String name : names) {
+ StackEntry entry = new StackEntry();
+ entry.name = name;
+
+ ReftableReader t = null;
+ if (current.containsKey(name)) {
+ t = current.remove(name);
+ } else {
+ File subtable = new File(reftableDir, name);
+ FileInputStream is;
+
+ is = new FileInputStream(subtable);
+
+ t = new ReftableReader(BlockSource.from(is));
+ newTables.add(t);
+ }
+
+ if (last != null) {
+ // TODO: move this to MergedReftable
+ if (last.maxUpdateIndex() >= t.minUpdateIndex()) {
+ throw new ReftableNumbersNotIncreasingException(name,
+ last.maxUpdateIndex(), t.minUpdateIndex());
+ }
+ }
+ last = t;
+
+ entry.reftableReader = t;
+ newStack.add(entry);
+ }
+ // survived without exceptions: swap in new stack, and close
+ // dangling tables.
+ stack = newStack;
+ newTables.clear();
+
+ current.values().forEach(r -> {
+ try {
+ r.close();
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ });
+ } finally {
+ newTables.forEach(t -> {
+ try {
+ t.close();
+ } catch (IOException ioe) {
+ // reader close should not generate errors.
+ throw new AssertionError(ioe);
+ }
+ });
+ }
+ }
+
+ void reload() throws IOException {
+ // Try for 2.5 seconds.
+ long deadline = System.currentTimeMillis() + 2500;
+ // A successful reftable transaction is 2 atomic file writes
+ // (open, write, close, rename), which a fast Linux system should be
+ // able to do in about ~200us. So 1 ms should be ample time.
+ long min = 1;
+ long max = 1000;
+ long delay = 0;
+ boolean success = false;
+
+ // Don't check deadline for the first 3 retries, so we can step with a
+ // debugger without worrying about deadlines.
+ int tries = 0;
+ while (tries < 3 || System.currentTimeMillis() < deadline) {
+ List<String> names = readTableNames();
+ tries++;
+ try {
+ reloadOnce(names);
+ success = true;
+ break;
+ } catch (FileNotFoundException e) {
+ List<String> changed = readTableNames();
+ if (changed.equals(names)) {
+ throw e;
+ }
+ }
+
+ delay = FileUtils.delay(delay, min, max);
+ try {
+ Thread.sleep(delay);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException(e);
+ }
+ }
+
+ if (!success) {
+ throw new LockFailedException(stackPath);
+ }
+
+ mergedReftable = new MergedReftable(stack.stream()
+ .map(x -> x.reftableReader).collect(Collectors.toList()));
+ long curr = nextUpdateIndex();
+ if (lastNextUpdateIndex > 0 && lastNextUpdateIndex != curr
+ && onChange != null) {
+ onChange.run();
+ }
+ lastNextUpdateIndex = curr;
+ }
+
+ /**
+ * @return the merged reftable
+ */
+ public MergedReftable getMergedReftable() {
+ return mergedReftable;
+ }
+
+ /**
+ * Writer is a callable that writes data to a reftable under construction.
+ * It should set the min/max update index, and then write refs and/or logs.
+ * It should not call finish() on the writer.
+ */
+ public interface Writer {
+ /**
+ * Write data to reftable
+ *
+ * @param w
+ * writer to use
+ * @throws IOException
+ */
+ void call(ReftableWriter w) throws IOException;
+ }
+
+ private List<String> readTableNames() throws IOException {
+ List<String> names = new ArrayList<>(stack.size() + 1);
+
+ try (BufferedReader br = new BufferedReader(
+ new InputStreamReader(new FileInputStream(stackPath), UTF_8))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ if (!line.isEmpty()) {
+ names.add(line);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // file isn't there: empty repository.
+ }
+ return names;
+ }
+
+ /**
+ * @return true if the on-disk file corresponds to the in-memory data.
+ * @throws IOException
+ * 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 {
+ List<String> names = readTableNames();
+ if (names.size() != stack.size()) {
+ return false;
+ }
+ for (int i = 0; i < names.size(); i++) {
+ if (!names.get(i).equals(stack.get(i).name)) {
+ return false;
+ }
+ }
+ } catch (FileNotFoundException e) {
+ return stack.isEmpty();
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close() {
+ for (StackEntry entry : stack) {
+ try {
+ entry.reftableReader.close();
+ } catch (Exception e) {
+ // we are reading; this should never fail.
+ throw new AssertionError(e);
+ }
+ }
+ }
+
+ private long nextUpdateIndex() throws IOException {
+ return stack.size() > 0
+ ? stack.get(stack.size() - 1).reftableReader.maxUpdateIndex()
+ + 1
+ : 1;
+ }
+
+ private String filename(long low, long high) {
+ return String.format("%012x-%012x", //$NON-NLS-1$
+ Long.valueOf(low), Long.valueOf(high));
+ }
+
+ /**
+ * Tries to add a new reftable to the stack. Returns true if it succeeded,
+ * or false if there was a lock failure, due to races with other processes.
+ * This is package private so FileReftableDatabase can call into here.
+ *
+ * @param w
+ * writer to write data to a reftable under construction
+ * @return true if the transaction was successful.
+ * @throws IOException
+ * on I/O problems
+ */
+ @SuppressWarnings("nls")
+ public boolean addReftable(Writer w) throws IOException {
+ LockFile lock = new LockFile(stackPath);
+ try {
+ if (!lock.lockForAppend()) {
+ return false;
+ }
+ if (!isUpToDate()) {
+ return false;
+ }
+
+ String fn = filename(nextUpdateIndex(), nextUpdateIndex());
+
+ File tmpTable = File.createTempFile(fn + "_", ".ref",
+ stackPath.getParentFile());
+
+ ReftableWriter.Stats s;
+ try (FileOutputStream fos = new FileOutputStream(tmpTable)) {
+ ReftableWriter rw = new ReftableWriter(reftableConfig(), fos);
+ w.call(rw);
+ rw.finish();
+ s = rw.getStats();
+ }
+
+ if (s.minUpdateIndex() < nextUpdateIndex()) {
+ return false;
+ }
+
+ // The spec says to name log-only files with .log, which is somewhat
+ // pointless given compaction, but we do so anyway.
+ fn += s.refCount() > 0 ? ".ref" : ".log";
+ File dest = new File(reftableDir, fn);
+
+ FileUtils.rename(tmpTable, dest, StandardCopyOption.ATOMIC_MOVE);
+ lock.write((fn + "\n").getBytes(UTF_8));
+ if (!lock.commit()) {
+ FileUtils.delete(dest);
+ return false;
+ }
+
+ reload();
+
+ autoCompact();
+ } finally {
+ lock.unlock();
+ }
+ return true;
+ }
+
+ private ReftableConfig reftableConfig() {
+ return new ReftableConfig(configSupplier.get());
+ }
+
+ /**
+ * Write the reftable for the given range into a temp file.
+ *
+ * @param first
+ * index of first stack entry to be written
+ * @param last
+ * index of last stack entry to be written
+ * @return the file holding the replacement table.
+ * @throws IOException
+ * on I/O problem
+ */
+ private File compactLocked(int first, int last) throws IOException {
+ String fn = filename(first, last);
+
+ File tmpTable = File.createTempFile(fn + "_", ".ref", //$NON-NLS-1$//$NON-NLS-2$
+ stackPath.getParentFile());
+ try (FileOutputStream fos = new FileOutputStream(tmpTable)) {
+ ReftableCompactor c = new ReftableCompactor(fos)
+ .setConfig(reftableConfig())
+ .setIncludeDeletes(first > 0);
+
+ List<ReftableReader> compactMe = new ArrayList<>();
+ long totalBytes = 0;
+ for (int i = first; i <= last; i++) {
+ compactMe.add(stack.get(i).reftableReader);
+ totalBytes += stack.get(i).reftableReader.size();
+ }
+ c.addAll(compactMe);
+
+ c.compact();
+
+ // Even though the compaction did not definitely succeed, we keep
+ // tally here as we've expended the effort.
+ stats.bytes += totalBytes;
+ stats.tables += first - last + 1;
+ stats.attempted++;
+ stats.refCount += c.getStats().refCount();
+ stats.logCount += c.getStats().logCount();
+ }
+
+ return tmpTable;
+ }
+
+ /**
+ * Compacts a range of the stack, following the file locking protocol
+ * documented in the spec.
+ *
+ * @param first
+ * index of first stack entry to be considered in compaction
+ * @param last
+ * index of last stack entry to be considered in compaction
+ * @return true if a compaction was successfully applied.
+ * @throws IOException
+ * on I/O problem
+ */
+ boolean compactRange(int first, int last) throws IOException {
+ if (first >= last) {
+ return true;
+ }
+ LockFile lock = new LockFile(stackPath);
+
+ File tmpTable = null;
+ List<LockFile> subtableLocks = new ArrayList<>();
+
+ try {
+ if (!lock.lock()) {
+ return false;
+ }
+ if (!isUpToDate()) {
+ return false;
+ }
+
+ List<File> deleteOnSuccess = new ArrayList<>();
+ for (int i = first; i <= last; i++) {
+ File f = new File(reftableDir, stack.get(i).name);
+ LockFile lf = new LockFile(f);
+ if (!lf.lock()) {
+ return false;
+ }
+ subtableLocks.add(lf);
+ deleteOnSuccess.add(f);
+ }
+
+ lock.unlock();
+ lock = null;
+
+ tmpTable = compactLocked(first, last);
+
+ lock = new LockFile(stackPath);
+ if (!lock.lock()) {
+ return false;
+ }
+ if (!isUpToDate()) {
+ return false;
+ }
+
+ String fn = filename(
+ stack.get(first).reftableReader.minUpdateIndex(),
+ stack.get(last).reftableReader.maxUpdateIndex());
+
+ // The spec suggests to use .log for log-only tables, and collect
+ // all log entries in a single file at the bottom of the stack. That would
+ // require supporting overlapping ranges for the different tables. For the
+ // sake of simplicity, we simply ignore this and always produce a log +
+ // ref combined table.
+ fn += ".ref"; //$NON-NLS-1$
+ File dest = new File(reftableDir, fn);
+
+ FileUtils.rename(tmpTable, dest, StandardCopyOption.ATOMIC_MOVE);
+ tmpTable = null;
+
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < first; i++) {
+ sb.append(stack.get(i).name + "\n"); //$NON-NLS-1$
+ }
+ sb.append(fn + "\n"); //$NON-NLS-1$
+ for (int i = last + 1; i < stack.size(); i++) {
+ sb.append(stack.get(i).name + "\n"); //$NON-NLS-1$
+ }
+
+ lock.write(sb.toString().getBytes(UTF_8));
+ if (!lock.commit()) {
+ dest.delete();
+ return false;
+ }
+
+ for (File f : deleteOnSuccess) {
+ Files.delete(f.toPath());
+ }
+
+ reload();
+ return true;
+ } finally {
+ if (tmpTable != null) {
+ tmpTable.delete();
+ }
+ for (LockFile lf : subtableLocks) {
+ lf.unlock();
+ }
+ if (lock != null) {
+ lock.unlock();
+ }
+ }
+ }
+
+ /**
+ * Calculate an approximate log2.
+ *
+ * @param sz
+ * @return log2
+ */
+ static int log(long sz) {
+ long base = 2;
+ if (sz <= 0) {
+ throw new IllegalArgumentException("log2 negative"); //$NON-NLS-1$
+ }
+ int l = 0;
+ while (sz > 0) {
+ l++;
+ sz /= base;
+ }
+
+ return l - 1;
+ }
+
+ /**
+ * A segment is a consecutive list of reftables of the same approximate
+ * size.
+ */
+ static class Segment {
+ // the approximate log_2 of the size.
+ int log;
+
+ // The total bytes in this segment
+ long bytes;
+
+ int start;
+
+ int end; // exclusive.
+
+ int size() {
+ return end - start;
+ }
+
+ Segment(int start, int end, int log, long bytes) {
+ this.log = log;
+ this.start = start;
+ this.end = end;
+ this.bytes = bytes;
+ }
+
+ Segment() {
+ this(0, 0, 0, 0);
+ }
+
+ @Override
+ public int hashCode() {
+ return 0; // appease error-prone
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ Segment o = (Segment) other;
+ return o.bytes == bytes && o.log == log && o.start == start
+ && o.end == end;
+ }
+
+ @SuppressWarnings("boxing")
+ @Override
+ public String toString() {
+ return String.format("{ [%d,%d) l=%d sz=%d }", start, end, log, //$NON-NLS-1$
+ bytes);
+ }
+ }
+
+ static List<Segment> segmentSizes(long[] sizes) {
+ List<Segment> segments = new ArrayList<>();
+ Segment cur = new Segment();
+ for (int i = 0; i < sizes.length; i++) {
+ int l = log(sizes[i]);
+ if (l != cur.log && cur.bytes > 0) {
+ segments.add(cur);
+ cur = new Segment();
+ cur.start = i;
+ cur.log = l;
+ }
+
+ cur.log = l;
+ cur.end = i + 1;
+ cur.bytes += sizes[i];
+ }
+ segments.add(cur);
+ return segments;
+ }
+
+ private static Optional<Segment> autoCompactCandidate(long[] sizes) {
+ if (sizes.length == 0) {
+ return Optional.empty();
+ }
+
+ // The cost of compaction is proportional to the size, and we want to
+ // avoid frequent large compactions. We do this by playing the game 2048
+ // here: first compact together the smallest tables if there are more
+ // than one. Then try to see if the result will be big enough to match
+ // up with next up.
+
+ List<Segment> segments = segmentSizes(sizes);
+ segments = segments.stream().filter(s -> s.size() > 1)
+ .collect(Collectors.toList());
+ if (segments.isEmpty()) {
+ return Optional.empty();
+ }
+
+ Optional<Segment> optMinSeg = segments.stream()
+ .min(Comparator.comparing(s -> Integer.valueOf(s.log)));
+ // Input is non-empty, so always present.
+ Segment smallCollected = optMinSeg.get();
+ while (smallCollected.start > 0) {
+ int prev = smallCollected.start - 1;
+ long prevSize = sizes[prev];
+ if (log(smallCollected.bytes) < log(prevSize)) {
+ break;
+ }
+ smallCollected.start = prev;
+ smallCollected.bytes += prevSize;
+ }
+
+ return Optional.of(smallCollected);
+ }
+
+ /**
+ * Heuristically tries to compact the stack if the stack has a suitable
+ * shape.
+ *
+ * @throws IOException
+ */
+ private void autoCompact() throws IOException {
+ Optional<Segment> cand = autoCompactCandidate(tableSizes());
+ if (cand.isPresent()) {
+ if (!compactRange(cand.get().start, cand.get().end - 1)) {
+ stats.failed++;
+ }
+ }
+ }
+
+ // 68b footer, 24b header = 92.
+ private static long OVERHEAD = 91;
+
+ private long[] tableSizes() throws IOException {
+ long[] sizes = new long[stack.size()];
+ for (int i = 0; i < stack.size(); i++) {
+ // If we don't subtract the overhead, the file size isn't
+ // proportional to the number of entries. This will cause us to
+ // compact too often, which is expensive.
+ sizes[i] = stack.get(i).reftableReader.size() - OVERHEAD;
+ }
+ return sizes;
+ }
+
+ void compactFully() throws IOException {
+ if (!compactRange(0, stack.size() - 1)) {
+ stats.failed++;
+ }
+ }
+}
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 4f5f8a613e..9929c46af6 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
@@ -46,13 +46,20 @@
package org.eclipse.jgit.internal.storage.file;
+import static java.util.stream.Collectors.toList;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.text.MessageFormat;
import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
@@ -68,21 +75,26 @@ import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle;
import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateRepository;
import org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase;
import org.eclipse.jgit.lib.BaseRepositoryBuilder;
+import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig.HideDotFiles;
import org.eclipse.jgit.lib.CoreConfig.SymLinks;
+import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
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.lib.ReflogEntry;
import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.lib.Repository;
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;
import org.eclipse.jgit.util.RawParseUtils;
@@ -121,7 +133,7 @@ public class FileRepository extends Repository {
private static final String UNNAMED = "Unnamed repository; edit this file to name it for gitweb."; //$NON-NLS-1$
private final FileBasedConfig repoConfig;
- private final RefDatabase refs;
+ private RefDatabase refs;
private final ObjectDirectory objectDatabase;
private final Object snapshotLock = new Object();
@@ -197,9 +209,13 @@ public class FileRepository extends Repository {
ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
String reftype = repoConfig.getString(
- "extensions", null, "refStorage"); //$NON-NLS-1$ //$NON-NLS-2$
+ ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
+ ConfigConstants.CONFIG_KEY_REF_STORAGE);
if (repositoryFormatVersion >= 1 && reftype != null) {
- if (StringUtils.equalsIgnoreCase(reftype, "reftree")) { //$NON-NLS-1$
+ if (StringUtils.equalsIgnoreCase(reftype,
+ ConfigConstants.CONFIG_REF_STORAGE_REFTABLE)) {
+ refs = new FileReftableDatabase(this);
+ } else if (StringUtils.equalsIgnoreCase(reftype, "reftree")) { //$NON-NLS-1$
refs = new RefTreeDatabase(this, new RefDirectory(this));
} else {
throw new IOException(JGitText.get().unknownRepositoryFormat);
@@ -358,9 +374,8 @@ public class FileRepository extends Repository {
File directory = getDirectory();
if (directory != null) {
return directory.getPath();
- } else {
- throw new IllegalStateException();
}
+ throw new IllegalStateException();
}
/** {@inheritDoc} */
@@ -531,10 +546,19 @@ public class FileRepository extends Repository {
/** {@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 new ReflogReaderImpl(this, ref.getName());
- return null;
+ if (ref == null) {
+ return null;
+ }
+ return new ReflogReaderImpl(this, ref.getName());
}
/** {@inheritDoc} */
@@ -614,4 +638,218 @@ public class FileRepository extends Repository {
throw new JGitInternalException(JGitText.get().gcFailed, e);
}
}
+
+ /**
+ * Converts the RefDatabase from reftable to RefDirectory. This operation is
+ * not atomic.
+ *
+ * @param writeLogs
+ * whether to write reflogs
+ * @param backup
+ * whether to rename or delete the old storage files. If set to
+ * {@code true}, the reftable list is left in {@code refs.old},
+ * and the {@code reftable/} dir is left alone. If set to
+ * {@code false}, the {@code reftable/} dir is removed, and
+ * {@code refs} file is removed.
+ * @throws IOException
+ * on IO problem
+ */
+ void convertToPackedRefs(boolean writeLogs, boolean backup) throws IOException {
+ List<Ref> all = refs.getRefs();
+ File packedRefs = new File(getDirectory(), 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 refsHeadsFile = new File(refsFile, "heads");//$NON-NLS-1$
+ File headFile = new File(getDirectory(), Constants.HEAD);
+ FileReftableDatabase oldDb = (FileReftableDatabase) refs;
+
+ // Remove the dummy files that ensure compatibility with older git
+ // versions (see convertToReftable). First make room for refs/heads/
+ refsHeadsFile.delete();
+ // RefDirectory wants to create the refs/ directory from scratch, so
+ // remove that too.
+ refsFile.delete();
+ // remove HEAD so its previous invalid value doesn't cause issues.
+ headFile.delete();
+
+ // This is not atomic, but there is no way to instantiate a RefDirectory
+ // that is disconnected from the current repo.
+ RefDirectory refDir = new RefDirectory(this);
+ refs = refDir;
+ refs.create();
+
+ ReflogWriter logWriter = refDir.newLogWriter(true);
+ List<Ref> symrefs = new ArrayList<>();
+ BatchRefUpdate bru = refs.newBatchUpdate();
+ for (Ref r : all) {
+ if (r.isSymbolic()) {
+ symrefs.add(r);
+ } else {
+ bru.addCommand(new ReceiveCommand(ObjectId.zeroId(),
+ r.getObjectId(), r.getName()));
+ }
+
+ if (writeLogs) {
+ List<ReflogEntry> logs = oldDb.getReflogReader(r.getName())
+ .getReverseEntries();
+ Collections.reverse(logs);
+ for (ReflogEntry e : logs) {
+ logWriter.log(r.getName(), e);
+ }
+ }
+ }
+
+ try (RevWalk rw = new RevWalk(this)) {
+ bru.execute(rw, NullProgressMonitor.INSTANCE);
+ }
+
+ List<String> failed = new ArrayList<>();
+ for (ReceiveCommand cmd : bru.getCommands()) {
+ if (cmd.getResult() != ReceiveCommand.Result.OK) {
+ failed.add(cmd.getRefName() + ": " + cmd.getResult()); //$NON-NLS-1$
+ }
+ }
+
+ if (!failed.isEmpty()) {
+ throw new IOException(String.format("%s: %s", //$NON-NLS-1$
+ JGitText.get().failedToConvert,
+ StringUtils.join(failed, ", "))); //$NON-NLS-1$
+ }
+
+ for (Ref s : symrefs) {
+ RefUpdate up = refs.newUpdate(s.getName(), false);
+ up.setForceUpdate(true);
+ RefUpdate.Result res = up.link(s.getTarget().getName());
+ if (res != RefUpdate.Result.NEW
+ && res != RefUpdate.Result.NO_CHANGE) {
+ throw new IOException(
+ String.format("ref %s: %s", s.getName(), res)); //$NON-NLS-1$
+ }
+ }
+
+ if (!backup) {
+ File reftableDir = new File(getDirectory(), Constants.REFTABLE);
+ FileUtils.delete(reftableDir,
+ FileUtils.RECURSIVE | FileUtils.IGNORE_ERRORS);
+ }
+ repoConfig.unset(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
+ ConfigConstants.CONFIG_KEY_REF_STORAGE);
+ }
+
+ /**
+ * Converts the RefDatabase from RefDirectory to reftable. This operation is
+ * not atomic.
+ *
+ * @param writeLogs
+ * whether to write reflogs
+ * @param backup
+ * whether to rename or delete the old storage files. If set to
+ * {@code true}, the loose refs are left in {@code refs.old}, the
+ * packed-refs in {@code packed-refs.old} and reflogs in
+ * {@code refs.old/}. HEAD is left in {@code HEAD.old} and also
+ * {@code .log} is appended to additional refs. If set to
+ * {@code false}, the {@code refs/} and {@code logs/} directories
+ * and {@code HEAD} and additional symbolic refs are removed.
+ * @throws IOException
+ * on IO problem
+ */
+ @SuppressWarnings("nls")
+ void convertToReftable(boolean writeLogs, boolean backup)
+ throws IOException {
+ File reftableDir = new File(getDirectory(), Constants.REFTABLE);
+ File headFile = new File(getDirectory(), Constants.HEAD);
+ if (reftableDir.exists() && reftableDir.listFiles().length > 0) {
+ throw new IOException(JGitText.get().reftableDirExists);
+ }
+
+ // Ignore return value, as it is tied to temporary newRefs file.
+ FileReftableDatabase.convertFrom(this, writeLogs);
+
+ File refsFile = new File(getDirectory(), "refs");
+
+ // non-atomic: remove old data.
+ File packedRefs = new File(getDirectory(), Constants.PACKED_REFS);
+ File logsDir = new File(getDirectory(), 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"));
+ if (packedRefs.exists()) {
+ FileUtils.rename(packedRefs, new File(getDirectory(),
+ Constants.PACKED_REFS + ".old"));
+ }
+ if (logsDir.exists()) {
+ FileUtils.rename(logsDir,
+ new File(getDirectory(), Constants.LOGS + ".old"));
+ }
+ for (String r : additional) {
+ FileUtils.rename(new File(getDirectory(), r),
+ new File(getDirectory(), r + ".old"));
+ }
+ } else {
+ FileUtils.delete(packedRefs, FileUtils.SKIP_MISSING);
+ FileUtils.delete(headFile);
+ FileUtils.delete(logsDir, FileUtils.RECURSIVE);
+ FileUtils.delete(refsFile, FileUtils.RECURSIVE);
+ for (String r : additional) {
+ new File(getDirectory(), r).delete();
+ }
+ }
+
+ FileUtils.mkdir(refsFile, true);
+
+ // By putting in a dummy HEAD, old versions of Git still detect a repo
+ // (that they can't read)
+ try (OutputStream os = new FileOutputStream(headFile)) {
+ os.write(Constants.encodeASCII("ref: refs/heads/.invalid"));
+ }
+
+ // Some tools might write directly into .git/refs/heads/BRANCH. By
+ // putting a file here, this fails spectacularly.
+ FileUtils.createNewFile(new File(refsFile, "heads"));
+
+ repoConfig.setString(ConfigConstants.CONFIG_EXTENSIONS_SECTION, null,
+ ConfigConstants.CONFIG_KEY_REF_STORAGE,
+ ConfigConstants.CONFIG_REF_STORAGE_REFTABLE);
+ repoConfig.setLong(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 1);
+ repoConfig.save();
+ refs.close();
+ refs = new FileReftableDatabase(this);
+ }
+
+ /**
+ * Converts between ref storage formats.
+ *
+ * @param format
+ * the format to convert to, either "reftable" or "refdir"
+ * @param writeLogs
+ * whether to write reflogs
+ * @param backup
+ * whether to make a backup of the old data
+ * @throws IOException
+ * on I/O problems.
+ */
+ @SuppressWarnings("nls")
+ public void convertRefStorage(String format, boolean writeLogs,
+ boolean backup) throws IOException {
+ if (format.equals("reftable")) { //$NON-NLS-1$
+ if (refs instanceof RefDirectory) {
+ convertToReftable(writeLogs, backup);
+ }
+ } else if (format.equals("refdir")) {//$NON-NLS-1$
+ if (refs instanceof FileReftableDatabase) {
+ convertToPackedRefs(writeLogs, backup);
+ }
+ } else {
+ throw new IOException(String.format(
+ "unknown supported ref storage format '%s'", format));
+ }
+ }
}
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 0ff3f615bf..5edcf37420 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
@@ -131,7 +131,7 @@ import org.slf4j.LoggerFactory;
* adapted to FileRepositories.
*/
public class GC {
- private final static Logger LOG = LoggerFactory
+ private static final Logger LOG = LoggerFactory
.getLogger(GC.class);
private static final String PRUNE_EXPIRE_DEFAULT = "2.weeks.ago"; //$NON-NLS-1$
@@ -773,13 +773,26 @@ public class GC {
}
/**
- * Packs all non-symbolic, loose refs into packed-refs.
+ * 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 {
- Collection<Ref> refs = repo.getRefDatabase()
- .getRefsByPrefix(Constants.R_REFS);
+ 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 {
@@ -897,7 +910,10 @@ public class GC {
throw new IOException(e);
}
prunePacked();
- deleteEmptyRefsFolders();
+ if (repo.getRefDatabase() instanceof RefDirectory) {
+ // TODO: abstract this more cleanly.
+ deleteEmptyRefsFolders();
+ }
deleteOrphans();
deleteTempPacksIdx();
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 82458c1acf..13b9e79be8 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
@@ -152,11 +152,10 @@ class GcLog {
boolean commit() {
if (nonEmpty) {
return lock.commit();
- } else {
- logFile.delete();
- lock.unlock();
- return true;
}
+ logFile.delete();
+ lock.unlock();
+ return true;
}
/**
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 6af41256d6..78262e9773 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
@@ -1,45 +1,12 @@
/*
* Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
+ * Copyright (C) 2006-2021, 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 v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
+ * 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.
*
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.internal.storage.file;
@@ -83,7 +50,7 @@ import org.slf4j.LoggerFactory;
* name.
*/
public class LockFile {
- private final static Logger LOG = LoggerFactory.getLogger(LockFile.class);
+ private static final Logger LOG = LoggerFactory.getLogger(LockFile.class);
/**
* Unlock the given file.
@@ -129,11 +96,15 @@ public class LockFile {
private boolean haveLck;
- FileOutputStream os;
+ private FileOutputStream os;
private boolean needSnapshot;
- boolean fsync;
+ private boolean fsync;
+
+ private boolean isAppend;
+
+ private boolean written;
private FileSnapshot commitSnapshot;
@@ -160,6 +131,10 @@ public class LockFile {
* does not hold the lock.
*/
public boolean lock() throws IOException {
+ if (haveLck) {
+ throw new IllegalStateException(
+ MessageFormat.format(JGitText.get().lockAlreadyHeld, ref));
+ }
FileUtils.mkdirs(lck.getParentFile(), true);
try {
token = FS.DETECTED.createNewFileAtomic(lck);
@@ -167,18 +142,15 @@ public class LockFile {
LOG.error(JGitText.get().failedCreateLockFile, lck, e);
throw e;
}
- if (token.isCreated()) {
+ boolean obtainedLock = token.isCreated();
+ if (obtainedLock) {
haveLck = true;
- try {
- os = new FileOutputStream(lck);
- } catch (IOException ioe) {
- unlock();
- throw ioe;
- }
+ isAppend = false;
+ written = false;
} else {
closeToken();
}
- return haveLck;
+ return obtainedLock;
}
/**
@@ -191,12 +163,24 @@ public class LockFile {
* does not hold the lock.
*/
public boolean lockForAppend() throws IOException {
- if (!lock())
+ if (!lock()) {
return false;
+ }
copyCurrentContent();
+ isAppend = true;
+ written = false;
return true;
}
+ // For tests only
+ boolean isLocked() {
+ return haveLck;
+ }
+
+ private FileOutputStream getStream() throws IOException {
+ return new FileOutputStream(lck, isAppend);
+ }
+
/**
* Copy the current file content into the temporary file.
* <p>
@@ -218,32 +202,31 @@ public class LockFile {
*/
public void copyCurrentContent() throws IOException {
requireLock();
- try {
+ try (FileOutputStream out = getStream()) {
try (FileInputStream fis = new FileInputStream(ref)) {
if (fsync) {
FileChannel in = fis.getChannel();
long pos = 0;
long cnt = in.size();
while (0 < cnt) {
- long r = os.getChannel().transferFrom(in, pos, cnt);
+ long r = out.getChannel().transferFrom(in, pos, cnt);
pos += r;
cnt -= r;
}
} else {
final byte[] buf = new byte[2048];
int r;
- while ((r = fis.read(buf)) >= 0)
- os.write(buf, 0, r);
+ while ((r = fis.read(buf)) >= 0) {
+ out.write(buf, 0, r);
+ }
}
+ } catch (FileNotFoundException fnfe) {
+ if (ref.exists()) {
+ throw fnfe;
+ }
+ // Don't worry about a file that doesn't exist yet, it
+ // conceptually has no current content to copy.
}
- } catch (FileNotFoundException fnfe) {
- if (ref.exists()) {
- unlock();
- throw fnfe;
- }
- // Don't worry about a file that doesn't exist yet, it
- // conceptually has no current content to copy.
- //
} catch (IOException | RuntimeException | Error ioe) {
unlock();
throw ioe;
@@ -286,18 +269,22 @@ public class LockFile {
*/
public void write(byte[] content) throws IOException {
requireLock();
- try {
+ try (FileOutputStream out = getStream()) {
+ if (written) {
+ throw new IOException(MessageFormat
+ .format(JGitText.get().lockStreamClosed, ref));
+ }
if (fsync) {
- FileChannel fc = os.getChannel();
+ FileChannel fc = out.getChannel();
ByteBuffer buf = ByteBuffer.wrap(content);
- while (0 < buf.remaining())
+ while (0 < buf.remaining()) {
fc.write(buf);
+ }
fc.force(true);
} else {
- os.write(content);
+ out.write(content);
}
- os.close();
- os = null;
+ written = true;
} catch (IOException | RuntimeException | Error ioe) {
unlock();
throw ioe;
@@ -316,36 +303,67 @@ public class LockFile {
public OutputStream getOutputStream() {
requireLock();
- final OutputStream out;
- if (fsync)
- out = Channels.newOutputStream(os.getChannel());
- else
- out = os;
+ if (written || os != null) {
+ throw new IllegalStateException(MessageFormat
+ .format(JGitText.get().lockStreamMultiple, ref));
+ }
return new OutputStream() {
+
+ private OutputStream out;
+
+ private boolean closed;
+
+ private OutputStream get() throws IOException {
+ if (written) {
+ throw new IOException(MessageFormat
+ .format(JGitText.get().lockStreamMultiple, ref));
+ }
+ if (out == null) {
+ os = getStream();
+ if (fsync) {
+ out = Channels.newOutputStream(os.getChannel());
+ } else {
+ out = os;
+ }
+ }
+ return out;
+ }
+
@Override
- public void write(byte[] b, int o, int n)
- throws IOException {
- out.write(b, o, n);
+ public void write(byte[] b, int o, int n) throws IOException {
+ get().write(b, o, n);
}
@Override
public void write(byte[] b) throws IOException {
- out.write(b);
+ get().write(b);
}
@Override
public void write(int b) throws IOException {
- out.write(b);
+ get().write(b);
}
@Override
public void close() throws IOException {
+ if (closed) {
+ return;
+ }
+ closed = true;
try {
- if (fsync)
- os.getChannel().force(true);
- out.close();
- os = null;
+ if (written) {
+ throw new IOException(MessageFormat
+ .format(JGitText.get().lockStreamClosed, ref));
+ }
+ if (out != null) {
+ if (fsync) {
+ os.getChannel().force(true);
+ }
+ out.close();
+ os = null;
+ }
+ written = true;
} catch (IOException | RuntimeException | Error ioe) {
unlock();
throw ioe;
@@ -355,7 +373,7 @@ public class LockFile {
}
void requireLock() {
- if (os == null) {
+ if (!haveLck) {
unlock();
throw new IllegalStateException(MessageFormat.format(JGitText.get().lockOnNotHeld, ref));
}
@@ -444,6 +462,8 @@ public class LockFile {
try {
FileUtils.rename(lck, ref, StandardCopyOption.ATOMIC_MOVE);
haveLck = false;
+ isAppend = false;
+ written = false;
closeToken();
return true;
} catch (IOException e) {
@@ -530,6 +550,8 @@ public class LockFile {
closeToken();
}
}
+ isAppend = false;
+ written = false;
}
/** {@inheritDoc} */
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 968ade6700..b4cad35ddf 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
@@ -109,7 +109,7 @@ import org.slf4j.LoggerFactory;
* considered.
*/
public class ObjectDirectory extends FileObjectDatabase {
- private final static Logger LOG = LoggerFactory
+ private static final Logger LOG = LoggerFactory
.getLogger(ObjectDirectory.class);
private static final PackList NO_PACKS = new PackList(
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 e5a54e372c..09f2021686 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
@@ -109,10 +109,9 @@ class ObjectDirectoryInserter extends ObjectInserter {
ObjectId id = idFor(type, data, off, len);
if (!createDuplicate && db.has(id)) {
return id;
- } else {
- File tmp = toTemp(type, data, off, len);
- return insertOneObject(tmp, id, createDuplicate);
}
+ File tmp = toTemp(type, data, off, len);
+ return insertOneObject(tmp, id, createDuplicate);
}
/** {@inheritDoc} */
@@ -141,12 +140,11 @@ class ObjectDirectoryInserter extends ObjectInserter {
int actLen = IO.readFully(is, buf, 0);
return insert(type, buf, 0, actLen, createDuplicate);
- } else {
- SHA1 md = digest();
- File tmp = toTemp(md, type, len, is);
- ObjectId id = md.toObjectId();
- return insertOneObject(tmp, id, createDuplicate);
}
+ SHA1 md = digest();
+ File tmp = toTemp(md, type, len, is);
+ ObjectId id = md.toObjectId();
+ return insertOneObject(tmp, id, createDuplicate);
}
private ObjectId insertOneObject(
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 86e90c63c5..e7d917bb7a 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
@@ -103,7 +103,7 @@ import org.slf4j.LoggerFactory;
* objects are similar.
*/
public class PackFile implements Iterable<PackIndex.MutableEntry> {
- private final static Logger LOG = LoggerFactory.getLogger(PackFile.class);
+ private static final Logger LOG = LoggerFactory.getLogger(PackFile.class);
/** Sorts PackFiles to be most recently created to least recently created. */
public static final Comparator<PackFile> SORT = new Comparator<PackFile>() {
@Override
@@ -845,19 +845,20 @@ public class PackFile implements Iterable<PackIndex.MutableEntry> {
case Constants.OBJ_TREE:
case Constants.OBJ_BLOB:
case Constants.OBJ_TAG: {
- if (delta != null || sz < curs.getStreamFileThreshold())
+ if (delta != null || sz < curs.getStreamFileThreshold()) {
data = decompress(pos + p, (int) sz, curs);
+ }
if (delta != null) {
type = typeCode;
break SEARCH;
}
- if (data != null)
+ if (data != null) {
return new ObjectLoader.SmallObject(typeCode, data);
- else
- return new LargePackedWholeObject(typeCode, sz, pos, p,
- this, curs.db);
+ }
+ return new LargePackedWholeObject(typeCode, sz, pos, p,
+ this, curs.db);
}
case Constants.OBJ_OFS_DELTA: {
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 e45b53ea68..9023570607 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
@@ -366,12 +366,8 @@ class PackedBatchRefUpdate extends BatchRefUpdate {
List<ReceiveCommand> commands) throws IOException {
// Construct a new RefList by merging the old list with the updates.
// This assumes that each ref occurs at most once as a ReceiveCommand.
- Collections.sort(commands, new Comparator<ReceiveCommand>() {
- @Override
- public int compare(ReceiveCommand a, ReceiveCommand b) {
- return a.getRefName().compareTo(b.getRefName());
- }
- });
+ Collections.sort(commands,
+ Comparator.comparing(ReceiveCommand::getRefName));
int delta = 0;
for (ReceiveCommand c : commands) {
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 6cb8a19f81..ae8fe0f562 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
@@ -130,7 +130,7 @@ import org.slf4j.LoggerFactory;
* overall size of a Git repository on disk.
*/
public class RefDirectory extends RefDatabase {
- private final static Logger LOG = LoggerFactory
+ private static final Logger LOG = LoggerFactory
.getLogger(RefDirectory.class);
/** Magic string denoting the start of a symbolic reference file. */
@@ -565,10 +565,9 @@ public class RefDirectory extends RefDatabase {
if (obj instanceof RevTag) {
return new ObjectIdRef.PeeledTag(leaf.getStorage(), leaf
.getName(), leaf.getObjectId(), rw.peel(obj).copy());
- } else {
- return new ObjectIdRef.PeeledNonTag(leaf.getStorage(), leaf
- .getName(), leaf.getObjectId());
}
+ return new ObjectIdRef.PeeledNonTag(leaf.getStorage(),
+ leaf.getName(), leaf.getObjectId());
}
}
@@ -865,10 +864,9 @@ public class RefDirectory extends RefDatabase {
if (peeledObjectId != null) {
return new ObjectIdRef.PeeledTag(PACKED, f.getName(),
f.getObjectId(), peeledObjectId);
- } else {
- return new ObjectIdRef.PeeledNonTag(PACKED, f.getName(),
- f.getObjectId());
}
+ return new ObjectIdRef.PeeledNonTag(PACKED, f.getName(),
+ f.getObjectId());
}
void log(boolean force, RefUpdate update, String msg, boolean deref)
@@ -1383,7 +1381,7 @@ public class RefDirectory extends RefDatabase {
LooseRef peel(ObjectIdRef newLeaf);
}
- private final static class LoosePeeledTag extends ObjectIdRef.PeeledTag
+ private static final class LoosePeeledTag extends ObjectIdRef.PeeledTag
implements LooseRef {
private final FileSnapshot snapShot;
@@ -1404,7 +1402,7 @@ public class RefDirectory extends RefDatabase {
}
}
- private final static class LooseNonTag extends ObjectIdRef.PeeledNonTag
+ private static final class LooseNonTag extends ObjectIdRef.PeeledNonTag
implements LooseRef {
private final FileSnapshot snapShot;
@@ -1425,7 +1423,7 @@ public class RefDirectory extends RefDatabase {
}
}
- private final static class LooseUnpeeled extends ObjectIdRef.Unpeeled
+ private static final class LooseUnpeeled extends ObjectIdRef.Unpeeled
implements LooseRef {
private FileSnapshot snapShot;
@@ -1455,14 +1453,12 @@ public class RefDirectory extends RefDatabase {
if (peeledObjectId != null) {
return new LoosePeeledTag(snapShot, getName(),
objectId, peeledObjectId);
- } else {
- return new LooseNonTag(snapShot, getName(),
- objectId);
}
+ return new LooseNonTag(snapShot, getName(), objectId);
}
}
- private final static class LooseSymbolicRef extends SymbolicRef implements
+ private static final class LooseSymbolicRef extends SymbolicRef implements
LooseRef {
private final FileSnapshot snapShot;
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 1a0d6953ab..dc4967fe4e 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
@@ -90,9 +90,8 @@ class RefDirectoryUpdate extends RefUpdate {
dst = database.findRef(name);
setOldObjectId(dst != null ? dst.getObjectId() : null);
return true;
- } else {
- return false;
}
+ return false;
}
/** {@inheritDoc} */
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 08a14b28d2..3cdd90408e 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
@@ -140,9 +140,9 @@ public class ReflogEntryImpl implements Serializable, ReflogEntry {
/** {@inheritDoc} */
@Override
public CheckoutEntry parseCheckout() {
- if (getComment().startsWith(CheckoutEntryImpl.CHECKOUT_MOVING_FROM))
+ if (getComment().startsWith(CheckoutEntryImpl.CHECKOUT_MOVING_FROM)) {
return new CheckoutEntryImpl(this);
- else
- return null;
+ }
+ return null;
}
}
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 131f716cf0..98d6ea00e7 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
@@ -61,6 +61,7 @@ import java.nio.channels.FileChannel;
import java.text.MessageFormat;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig;
import org.eclipse.jgit.lib.ObjectId;
@@ -68,6 +69,7 @@ import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FileUtils;
/**
@@ -239,7 +241,7 @@ public class ReflogWriter {
private ReflogWriter log(String refName, byte[] rec) throws IOException {
File log = refdb.logFor(refName);
boolean write = forceWrite
- || (isLogAllRefUpdates() && shouldAutoCreateLog(refName))
+ || shouldAutoCreateLog(refName)
|| log.isFile();
if (!write)
return this;
@@ -260,15 +262,27 @@ public class ReflogWriter {
return this;
}
- private boolean isLogAllRefUpdates() {
- return refdb.getRepository().getConfig().get(CoreConfig.KEY)
- .isLogAllRefUpdates();
- }
-
private boolean shouldAutoCreateLog(String refName) {
- return refName.equals(HEAD)
- || refName.startsWith(R_HEADS)
- || refName.startsWith(R_REMOTES)
- || refName.startsWith(R_NOTES);
+ Repository repo = refdb.getRepository();
+ CoreConfig.LogRefUpdates value = repo.isBare()
+ ? CoreConfig.LogRefUpdates.FALSE
+ : CoreConfig.LogRefUpdates.TRUE;
+ value = repo.getConfig().getEnum(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, value);
+ if (value != null) {
+ switch (value) {
+ case FALSE:
+ break;
+ case TRUE:
+ return refName.equals(HEAD) || refName.startsWith(R_HEADS)
+ || refName.startsWith(R_REMOTES)
+ || refName.startsWith(R_NOTES);
+ case ALWAYS:
+ return refName.equals(HEAD) || refName.startsWith(R_REFS);
+ default:
+ break;
+ }
+ }
+ return false;
}
} \ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java
index 79f1307578..fb06623d7e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java
@@ -139,49 +139,48 @@ public class UnpackedObject {
}
return new LargeObject(type, size, path, id, wc.db);
- } else {
- readSome(in, hdr, 2, 18);
- int c = hdr[0] & 0xff;
- int type = (c >> 4) & 7;
- long size = c & 15;
- int shift = 4;
- int p = 1;
- while ((c & 0x80) != 0) {
- c = hdr[p++] & 0xff;
- size += ((long) (c & 0x7f)) << shift;
- shift += 7;
- }
+ }
+ readSome(in, hdr, 2, 18);
+ int c = hdr[0] & 0xff;
+ int type = (c >> 4) & 7;
+ long size = c & 15;
+ int shift = 4;
+ int p = 1;
+ while ((c & 0x80) != 0) {
+ c = hdr[p++] & 0xff;
+ size += ((long) (c & 0x7f)) << shift;
+ shift += 7;
+ }
- switch (type) {
- case Constants.OBJ_COMMIT:
- case Constants.OBJ_TREE:
- case Constants.OBJ_BLOB:
- case Constants.OBJ_TAG:
- // Acceptable types for a loose object.
- break;
- default:
- throw new CorruptObjectException(id,
- JGitText.get().corruptObjectInvalidType);
- }
+ switch (type) {
+ case Constants.OBJ_COMMIT:
+ case Constants.OBJ_TREE:
+ case Constants.OBJ_BLOB:
+ case Constants.OBJ_TAG:
+ // Acceptable types for a loose object.
+ break;
+ default:
+ throw new CorruptObjectException(id,
+ JGitText.get().corruptObjectInvalidType);
+ }
- if (path == null && Integer.MAX_VALUE < size) {
- LargeObjectException.ExceedsByteArrayLimit e;
- e = new LargeObjectException.ExceedsByteArrayLimit();
- e.setObjectId(id);
- throw e;
- }
- if (size < wc.getStreamFileThreshold() || path == null) {
- in.reset();
- IO.skipFully(in, p);
- Inflater inf = wc.inflater();
- InputStream zIn = inflate(in, inf);
- byte[] data = new byte[(int) size];
- IO.readFully(zIn, data, 0, data.length);
- checkValidEndOfStream(in, inf, id, hdr);
- return new ObjectLoader.SmallObject(type, data);
- }
- return new LargeObject(type, size, path, id, wc.db);
+ if (path == null && Integer.MAX_VALUE < size) {
+ LargeObjectException.ExceedsByteArrayLimit e;
+ e = new LargeObjectException.ExceedsByteArrayLimit();
+ e.setObjectId(id);
+ throw e;
}
+ if (size < wc.getStreamFileThreshold() || path == null) {
+ in.reset();
+ IO.skipFully(in, p);
+ Inflater inf = wc.inflater();
+ InputStream zIn = inflate(in, inf);
+ byte[] data = new byte[(int) size];
+ IO.readFully(zIn, data, 0, data.length);
+ checkValidEndOfStream(in, inf, id, hdr);
+ return new ObjectLoader.SmallObject(type, data);
+ }
+ return new LargeObject(type, size, path, id, wc.db);
} catch (ZipException badStream) {
throw new CorruptObjectException(id,
JGitText.get().corruptObjectBadStream);
@@ -213,19 +212,18 @@ public class UnpackedObject {
JGitText.get().corruptObjectNegativeSize);
return size;
- } else {
- readSome(in, hdr, 2, 18);
- int c = hdr[0] & 0xff;
- long size = c & 15;
- int shift = 4;
- int p = 1;
- while ((c & 0x80) != 0) {
- c = hdr[p++] & 0xff;
- size += ((long) (c & 0x7f)) << shift;
- shift += 7;
- }
- return size;
}
+ readSome(in, hdr, 2, 18);
+ int c = hdr[0] & 0xff;
+ long size = c & 15;
+ int shift = 4;
+ int p = 1;
+ while ((c & 0x80) != 0) {
+ c = hdr[p++] & 0xff;
+ size += ((long) (c & 0x7f)) << shift;
+ shift += 7;
+ }
+ return size;
} catch (ZipException badStream) {
throw new CorruptObjectException(id,
JGitText.get().corruptObjectBadStream);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java
index ea0d269053..616447a651 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObjectCache.java
@@ -126,17 +126,19 @@ class UnpackedObjectCache {
for (int n = 0; n < MAX_CHAIN;) {
ObjectId obj = ids.get(i);
if (obj == null) {
- if (ids.compareAndSet(i, null, toAdd.copy()))
+ if (ids.compareAndSet(i, null, toAdd.copy())) {
return true;
- else
- continue;
+ }
+ continue;
}
- if (AnyObjectId.isEqual(obj, toAdd))
+ if (AnyObjectId.isEqual(obj, toAdd)) {
return true;
+ }
- if (++i == ids.length())
+ if (++i == ids.length()) {
i = 0;
+ }
n++;
}
return false;
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 5cbc2baeb2..9496d78c2f 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
@@ -76,14 +76,22 @@ public interface CachedPackUriProvider {
private final String hash;
private final String uri;
+ private final long size;
+
/**
* Constructs an object containing information about a packfile.
- * @param hash the hash of the packfile as a hexadecimal string
- * @param uri the URI corresponding to the packfile
+ *
+ * @param hash
+ * the hash of the packfile as a hexadecimal string
+ * @param uri
+ * the URI corresponding to the packfile
+ * @param size
+ * the size of the packfile in bytes
*/
- public PackInfo(String hash, String uri) {
+ public PackInfo(String hash, String uri, long size) {
this.hash = hash;
this.uri = uri;
+ this.size = size;
}
/**
@@ -99,5 +107,12 @@ public interface CachedPackUriProvider {
public String getUri() {
return uri;
}
+
+ /**
+ * @return the size of the packfile in bytes (-1 if unknown)
+ */
+ public long getSize() {
+ return size;
+ }
}
}
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 292fd368cd..b4143c900c 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
@@ -81,7 +81,7 @@ public class PackExt {
* the file extension.
* @return the PackExt for the ext
*/
- public synchronized static PackExt newPackExt(String ext) {
+ public static synchronized PackExt newPackExt(String ext) {
PackExt[] dst = new PackExt[VALUES.length + 1];
for (int i = 0; i < VALUES.length; i++) {
PackExt packExt = VALUES[i];
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 43067d364d..fcf9ca5791 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
@@ -225,7 +225,7 @@ public class PackWriter implements AutoCloseable {
}
@SuppressWarnings("unchecked")
- BlockList<ObjectToPack> objectsLists[] = new BlockList[OBJ_TAG + 1];
+ BlockList<ObjectToPack>[] objectsLists = new BlockList[OBJ_TAG + 1];
{
objectsLists[OBJ_COMMIT] = new BlockList<>();
objectsLists[OBJ_TREE] = new BlockList<>();
@@ -270,7 +270,7 @@ public class PackWriter implements AutoCloseable {
private List<ObjectToPack> sortedByName;
- private byte packcsum[];
+ private byte[] packcsum;
private boolean deltaBaseAsOffset;
@@ -1230,6 +1230,8 @@ public class PackWriter implements AutoCloseable {
if (packInfo != null) {
o.writeString(packInfo.getHash() + ' ' +
packInfo.getUri() + '\n');
+ stats.offloadedPackfiles += 1;
+ stats.offloadedPackfileSize += packInfo.getSize();
} else {
unwrittenCachedPacks.add(pack);
}
@@ -1749,23 +1751,23 @@ public class PackWriter implements AutoCloseable {
NullProgressMonitor.INSTANCE,
Collections.singleton(otp));
continue;
- } else {
- // Object writing already started, we cannot recover.
- //
- CorruptObjectException coe;
- coe = new CorruptObjectException(otp, ""); //$NON-NLS-1$
- coe.initCause(gone);
- throw coe;
}
+ // Object writing already started, we cannot recover.
+ //
+ CorruptObjectException coe;
+ coe = new CorruptObjectException(otp, ""); //$NON-NLS-1$
+ coe.initCause(gone);
+ throw coe;
}
}
// If we reached here, reuse wasn't possible.
//
- if (otp.isDeltaRepresentation())
+ if (otp.isDeltaRepresentation()) {
writeDeltaObjectDeflate(out, otp);
- else
+ } else {
writeWholeObjectDeflate(out, otp);
+ }
out.endObject();
otp.setCRC((int) crc32.getValue());
}
@@ -2430,7 +2432,7 @@ public class PackWriter implements AutoCloseable {
}
/** Possible states that a PackWriter can be in. */
- public static enum PackingPhase {
+ public enum PackingPhase {
/** Counting objects phase. */
COUNTING,
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 b66751b94c..6ae7e45efa 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
@@ -92,7 +92,6 @@ import org.eclipse.jgit.util.RawParseUtils;
class BlockReader {
private byte blockType;
private long endPosition;
- private boolean truncated;
private byte[] buf;
private int bufLen;
@@ -112,10 +111,6 @@ class BlockReader {
return blockType;
}
- boolean truncated() {
- return truncated;
- }
-
long endPosition() {
return endPosition;
}
@@ -331,16 +326,8 @@ class BlockReader {
// Log blocks must be inflated after the header.
long deflatedSize = inflateBuf(src, pos, blockLen, fileBlockSize);
endPosition = pos + 4 + deflatedSize;
- }
- if (bufLen < blockLen) {
- if (blockType != INDEX_BLOCK_TYPE) {
- throw invalidBlock();
- }
- // Its OK during sequential scan for an index block to have been
- // partially read and be truncated in-memory. This happens when
- // the index block is larger than the file's blockSize. Caller
- // will break out of its scan loop once it sees the blockType.
- truncated = true;
+ } else if (bufLen < blockLen) {
+ readBlockIntoBuf(src, pos, blockLen);
} else if (bufLen > blockLen) {
bufLen = blockLen;
}
@@ -405,7 +392,7 @@ class BlockReader {
}
void verifyIndex() throws IOException {
- if (blockType != INDEX_BLOCK_TYPE || truncated) {
+ if (blockType != INDEX_BLOCK_TYPE) {
throw invalidBlock();
}
}
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 b3173e838c..1ed1801678 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
@@ -286,7 +286,7 @@ class BlockWriter {
return aLen - bLen;
}
- static abstract class Entry {
+ abstract static class Entry {
static int compare(Entry ea, Entry eb) {
byte[] a = ea.key;
byte[] b = eb.key;
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 486fd28984..5c98242f10 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
@@ -45,6 +45,7 @@ package org.eclipse.jgit.internal.storage.reftable;
import java.io.IOException;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.lib.ReflogEntry;
/**
@@ -78,8 +79,9 @@ public abstract class LogCursor implements AutoCloseable {
/**
* Get current log entry.
*
- * @return current log entry.
+ * @return current log entry. Maybe null if we are producing deletions.
*/
+ @Nullable
public abstract ReflogEntry getReflogEntry();
/** {@inheritDoc} */
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 c740bf2c37..d272d09f00 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
@@ -67,13 +67,11 @@ import org.eclipse.jgit.lib.ReflogEntry;
* A {@code MergedReftable} is not thread-safe.
*/
public class MergedReftable extends Reftable {
- private final Reftable[] tables;
+ private final ReftableReader[] tables;
/**
* Initialize a merged table reader.
* <p>
- * The tables in {@code tableStack} will be closed when this
- * {@code MergedReftable} is closed.
*
* @param tableStack
* stack of tables to read from. The base of the stack is at
@@ -81,16 +79,44 @@ public class MergedReftable extends Reftable {
* {@code tableStack.size() - 1}. The top of the stack (higher
* index) shadows the base of the stack (lower index).
*/
- public MergedReftable(List<Reftable> tableStack) {
- tables = tableStack.toArray(new Reftable[0]);
+ public MergedReftable(List<ReftableReader> tableStack) {
+ tables = tableStack.toArray(new ReftableReader[0]);
// Tables must expose deletes to this instance to correctly
// shadow references from lower tables.
- for (Reftable t : tables) {
+ for (ReftableReader t : tables) {
t.setIncludeDeletes(true);
}
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long maxUpdateIndex() throws IOException {
+ return tables.length > 0 ? tables[tables.length - 1].maxUpdateIndex()
+ : 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long minUpdateIndex() throws IOException {
+ return tables.length > 0 ? tables[0].minUpdateIndex()
+ : 0;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean hasObjectMap() throws IOException {
+ boolean has = true;
+ for (int i = 0; has && i < tables.length; i++) {
+ has = has && tables[i].hasObjectMap();
+ }
+ return has;
+ }
+
/** {@inheritDoc} */
@Override
public RefCursor allRefs() throws IOException {
@@ -124,7 +150,7 @@ public class MergedReftable extends Reftable {
/** {@inheritDoc} */
@Override
public RefCursor byObjectId(AnyObjectId name) throws IOException {
- MergedRefCursor m = new MergedRefCursor();
+ MergedRefCursor m = new FilteringMergedRefCursor(name);
for (int i = 0; i < tables.length; i++) {
m.add(new RefQueueEntry(tables[i].byObjectId(name), i));
}
@@ -152,14 +178,6 @@ public class MergedReftable extends Reftable {
return m;
}
- /** {@inheritDoc} */
- @Override
- public void close() throws IOException {
- for (Reftable t : tables) {
- t.close();
- }
- }
-
int queueSize() {
return Math.max(1, tables.length);
}
@@ -251,6 +269,42 @@ public class MergedReftable extends Reftable {
}
}
+ private class FilteringMergedRefCursor extends MergedRefCursor {
+ final AnyObjectId filterId;
+ Ref filteredRef;
+
+ FilteringMergedRefCursor(AnyObjectId id) {
+ filterId = id;
+ filteredRef = null;
+ }
+
+ @Override
+ public Ref getRef() {
+ return filteredRef;
+ }
+
+ @Override
+ public boolean next() throws IOException {
+ for (;;) {
+ boolean ok = super.next();
+ if (!ok) {
+ return false;
+ }
+
+ String name = super.getRef().getName();
+
+ try (RefCursor c = seekRef(name)) {
+ if (c.next()) {
+ if (filterId.equals(c.getRef().getObjectId())) {
+ filteredRef = c.getRef();
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
private static class RefQueueEntry {
static int compare(RefQueueEntry a, RefQueueEntry b) {
int cmp = a.name().compareTo(b.name());
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 cb02628e8d..a7e8252b48 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
@@ -58,7 +58,7 @@ import org.eclipse.jgit.lib.SymbolicRef;
/**
* Abstract table of references.
*/
-public abstract class Reftable implements AutoCloseable {
+public abstract class Reftable {
/**
* References to convert into a reftable
*
@@ -72,9 +72,9 @@ public abstract class Reftable implements AutoCloseable {
cfg.setIndexObjects(false);
cfg.setAlignBlocks(false);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
- new ReftableWriter()
+ new ReftableWriter(buf)
.setConfig(cfg)
- .begin(buf)
+ .begin()
.sortAndWriteRefs(refs)
.finish();
return new ReftableReader(BlockSource.from(buf.toByteArray()));
@@ -99,6 +99,28 @@ public abstract class Reftable implements AutoCloseable {
}
/**
+ * Get the maximum update index for ref entries that appear in this
+ * reftable.
+ *
+ * @return the maximum update index for ref entries that appear in this
+ * reftable.
+ * @throws java.io.IOException
+ * file cannot be read.
+ */
+ public abstract long maxUpdateIndex() throws IOException;
+
+ /**
+ * Get the minimum update index for ref entries that appear in this
+ * reftable.
+ *
+ * @return the minimum update index for ref entries that appear in this
+ * reftable.
+ * @throws java.io.IOException
+ * file cannot be read.
+ */
+ public abstract long minUpdateIndex() throws IOException;
+
+ /**
* Seek to the first reference, to iterate in order.
*
* @return cursor to iterate.
@@ -149,6 +171,12 @@ public abstract class Reftable implements AutoCloseable {
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.
+ */
+ public abstract boolean hasObjectMap() throws IOException;
+
+ /**
* Seek reader to read log records.
*
* @return cursor to iterate; empty cursor if no logs are present.
@@ -282,8 +310,4 @@ public abstract class Reftable implements AutoCloseable {
}
return new SymbolicRef(ref.getName(), dst, ref.getUpdateIndex());
}
-
- /** {@inheritDoc} */
- @Override
- public abstract void close() throws IOException;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableBatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableBatchRefUpdate.java
index 07fd00f149..592120d89b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReftableBatchRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableBatchRefUpdate.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017, Google Inc.
+ * Copyright (C) 2019, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -41,40 +41,11 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.eclipse.jgit.internal.storage.dfs;
-
-import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
-import static org.eclipse.jgit.lib.Ref.Storage.NEW;
-import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
-import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
-import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
-import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
-import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_MISSING_OBJECT;
-import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
-import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.locks.ReentrantLock;
+package org.eclipse.jgit.internal.storage.reftable;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
-import org.eclipse.jgit.internal.storage.io.BlockSource;
-import org.eclipse.jgit.internal.storage.pack.PackExt;
-import org.eclipse.jgit.internal.storage.reftable.Reftable;
-import org.eclipse.jgit.internal.storage.reftable.ReftableCompactor;
-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.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.ObjectId;
@@ -82,43 +53,65 @@ 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.ReflogEntry;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.SymbolicRef;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.ReceiveCommand;
-/**
- * {@link org.eclipse.jgit.lib.BatchRefUpdate} for
- * {@link org.eclipse.jgit.internal.storage.dfs.DfsReftableDatabase}.
- */
-public class ReftableBatchRefUpdate extends BatchRefUpdate {
- private static final int AVG_BYTES = 36;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.locks.Lock;
+import java.util.stream.Collectors;
+
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
- private final DfsReftableDatabase refdb;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_MISSING_OBJECT;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.DELETE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
- private final DfsObjDatabase odb;
+/**
+ * {@link org.eclipse.jgit.lib.BatchRefUpdate} for Reftable based RefDatabase.
+ */
+public abstract class ReftableBatchRefUpdate extends BatchRefUpdate {
+ private final Lock lock;
- private final ReentrantLock lock;
+ private final ReftableDatabase refDb;
- private final ReftableConfig reftableConfig;
+ private final Repository repository;
/**
- * Initialize batch update.
+ * Initialize.
*
* @param refdb
- * database the update will modify.
- * @param odb
- * object database to store the reftable.
+ * The RefDatabase
+ * @param reftableDb
+ * The ReftableDatabase
+ * @param lock
+ * A lock protecting the refdatabase's state
+ * @param repository
+ * The repository on which this update will run
*/
- protected ReftableBatchRefUpdate(DfsReftableDatabase refdb,
- DfsObjDatabase odb) {
+ protected ReftableBatchRefUpdate(RefDatabase refdb, ReftableDatabase reftableDb, Lock lock,
+ Repository repository) {
super(refdb);
- this.refdb = refdb;
- this.odb = odb;
- lock = refdb.getLock();
- reftableConfig = refdb.getReftableConfig();
+ this.refDb = reftableDb;
+ this.lock = lock;
+ this.repository = repository;
}
/** {@inheritDoc} */
@@ -135,25 +128,36 @@ public class ReftableBatchRefUpdate extends BatchRefUpdate {
if (!checkObjectExistence(rw, pending)) {
return;
}
+ // if we are here, checkObjectExistence might have flagged some problems
+ // but the transaction is not atomic, so we should proceed with the other
+ // pending commands.
+ pending = getPending();
if (!checkNonFastForwards(rw, pending)) {
return;
}
+ pending = getPending();
lock.lock();
try {
- Reftable table = refdb.reader();
- if (!checkExpected(table, pending)) {
+ if (!checkExpected(pending)) {
return;
}
+ pending = getPending();
if (!checkConflicting(pending)) {
return;
}
+ pending = getPending();
if (!blockUntilTimestamps(MAX_WAIT)) {
return;
}
- applyUpdates(rw, pending);
+
+ List<Ref> newRefs = toNewRefs(rw, pending);
+ applyUpdates(newRefs, pending);
for (ReceiveCommand cmd : pending) {
- cmd.setResult(OK);
+ if (cmd.getResult() == NOT_ATTEMPTED) {
+ // XXX this is a bug in DFS ?
+ cmd.setResult(OK);
+ }
}
} finally {
lock.unlock();
@@ -164,6 +168,19 @@ public class ReftableBatchRefUpdate extends BatchRefUpdate {
}
}
+ /**
+ * Implements the storage-specific part of the update.
+ *
+ * @param newRefs
+ * the new refs to create
+ * @param pending
+ * the pending receive commands to be executed
+ * @throws IOException
+ * if any of the writes fail.
+ */
+ protected abstract void applyUpdates(List<Ref> newRefs,
+ List<ReceiveCommand> pending) throws IOException;
+
private List<ReceiveCommand> getPending() {
return ReceiveCommand.filter(getCommands(), NOT_ATTEMPTED);
}
@@ -181,8 +198,10 @@ public class ReftableBatchRefUpdate extends BatchRefUpdate {
// used for a missing object. Eagerly handle this case so we
// can set the right result.
cmd.setResult(REJECTED_MISSING_OBJECT);
- ReceiveCommand.abort(pending);
- return false;
+ if (isAtomic()) {
+ ReceiveCommand.abort(pending);
+ return false;
+ }
}
}
return true;
@@ -197,8 +216,10 @@ public class ReftableBatchRefUpdate extends BatchRefUpdate {
cmd.updateType(rw);
if (cmd.getType() == UPDATE_NONFASTFORWARD) {
cmd.setResult(REJECTED_NONFASTFORWARD);
- ReceiveCommand.abort(pending);
- return false;
+ if (isAtomic()) {
+ ReceiveCommand.abort(pending);
+ return false;
+ }
}
}
return true;
@@ -206,40 +227,55 @@ public class ReftableBatchRefUpdate extends BatchRefUpdate {
private boolean checkConflicting(List<ReceiveCommand> pending)
throws IOException {
- Set<String> names = new HashSet<>();
- for (ReceiveCommand cmd : pending) {
- names.add(cmd.getRefName());
- }
+ TreeSet<String> added = new TreeSet<>();
+ Set<String> deleted =
+ pending.stream()
+ .filter(cmd -> cmd.getType() == DELETE)
+ .map(c -> c.getRefName())
+ .collect(Collectors.toSet());
boolean ok = true;
for (ReceiveCommand cmd : pending) {
+ if (cmd.getType() == DELETE) {
+ continue;
+ }
+
String name = cmd.getRefName();
- if (refdb.isNameConflicting(name)) {
- cmd.setResult(LOCK_FAILURE);
- ok = false;
- } else {
- int s = name.lastIndexOf('/');
- while (0 < s) {
- if (names.contains(name.substring(0, s))) {
- cmd.setResult(LOCK_FAILURE);
- ok = false;
- break;
- }
- s = name.lastIndexOf('/', s - 1);
+ if (refDb.isNameConflicting(name, added, deleted)) {
+ if (isAtomic()) {
+ cmd.setResult(
+ ReceiveCommand.Result.REJECTED_OTHER_REASON, JGitText.get().transactionAborted);
+ } else {
+ cmd.setResult(LOCK_FAILURE);
}
+
+ ok = false;
}
+ added.add(name);
}
- if (!ok && isAtomic()) {
- ReceiveCommand.abort(pending);
- return false;
+
+ if (isAtomic()) {
+ if (!ok) {
+ pending.stream()
+ .filter(cmd -> cmd.getResult() == NOT_ATTEMPTED)
+ .forEach(cmd -> cmd.setResult(LOCK_FAILURE));
+ }
+ return ok;
}
- return ok;
+
+ for (ReceiveCommand cmd : pending) {
+ if (cmd.getResult() == NOT_ATTEMPTED) {
+ return true;
+ }
+ }
+
+ return false;
}
- private boolean checkExpected(Reftable table, List<ReceiveCommand> pending)
+ private boolean checkExpected(List<ReceiveCommand> pending)
throws IOException {
for (ReceiveCommand cmd : pending) {
- if (!matchOld(cmd, table.exactRef(cmd.getRefName()))) {
+ if (!matchOld(cmd, refDb.exactRef(cmd.getRefName()))) {
cmd.setResult(LOCK_FAILURE);
if (isAtomic()) {
ReceiveCommand.abort(pending);
@@ -253,7 +289,7 @@ public class ReftableBatchRefUpdate extends BatchRefUpdate {
private static boolean matchOld(ReceiveCommand cmd, @Nullable Ref ref) {
if (ref == null) {
return AnyObjectId.isEqual(ObjectId.zeroId(), cmd.getOldId())
- && cmd.getOldSymref() == null;
+ && cmd.getOldSymref() == null;
} else if (ref.isSymbolic()) {
return ref.getTarget().getName().equals(cmd.getOldSymref());
}
@@ -264,47 +300,26 @@ public class ReftableBatchRefUpdate extends BatchRefUpdate {
return cmd.getOldId().equals(id);
}
- private void applyUpdates(RevWalk rw, List<ReceiveCommand> pending)
- throws IOException {
- List<Ref> newRefs = toNewRefs(rw, pending);
- long updateIndex = nextUpdateIndex();
- Set<DfsPackDescription> prune = Collections.emptySet();
- DfsPackDescription pack = odb.newPack(PackSource.INSERT);
- try (DfsOutputStream out = odb.writeFile(pack, REFTABLE)) {
- ReftableConfig cfg = DfsPackCompactor
- .configureReftable(reftableConfig, out);
-
- ReftableWriter.Stats stats;
- if (refdb.compactDuringCommit()
- && newRefs.size() * AVG_BYTES <= cfg.getRefBlockSize()
- && canCompactTopOfStack(cfg)) {
- ByteArrayOutputStream tmp = new ByteArrayOutputStream();
- write(tmp, cfg, updateIndex, newRefs, pending);
- stats = compactTopOfStack(out, cfg, tmp.toByteArray());
- prune = toPruneTopOfStack();
- } else {
- stats = write(out, cfg, updateIndex, newRefs, pending);
- }
- pack.addFileExt(REFTABLE);
- pack.setReftableStats(stats);
- }
-
- odb.commitPack(Collections.singleton(pack), prune);
- odb.addReftable(pack, prune);
- refdb.clearCache();
- }
-
- private ReftableWriter.Stats write(OutputStream os, ReftableConfig cfg,
- long updateIndex, List<Ref> newRefs, List<ReceiveCommand> pending)
- throws IOException {
- ReftableWriter writer = new ReftableWriter(cfg)
- .setMinUpdateIndex(updateIndex).setMaxUpdateIndex(updateIndex)
- .begin(os).sortAndWriteRefs(newRefs);
+ /**
+ * Writes the refs to the writer, and calls finish.
+ *
+ * @param writer
+ * the writer on which we should write.
+ * @param newRefs
+ * the ref data to write..
+ * @param pending
+ * the log data to write.
+ * @throws IOException
+ * in case of problems.
+ */
+ protected void write(ReftableWriter writer, List<Ref> newRefs,
+ List<ReceiveCommand> pending) throws IOException {
+ long updateIndex = refDb.nextUpdateIndex();
+ writer.setMinUpdateIndex(updateIndex).setMaxUpdateIndex(updateIndex)
+ .begin().sortAndWriteRefs(newRefs);
if (!isRefLogDisabled()) {
writeLog(writer, updateIndex, pending);
}
- writer.finish();
- return writer.getStats();
}
private void writeLog(ReftableWriter writer, long updateIndex,
@@ -319,7 +334,7 @@ public class ReftableBatchRefUpdate extends BatchRefUpdate {
PersonIdent ident = getRefLogIdent();
if (ident == null) {
- ident = new PersonIdent(refdb.getRepository());
+ ident = new PersonIdent(repository);
}
for (String name : byName) {
ReceiveCommand cmd = cmds.get(name);
@@ -361,20 +376,25 @@ public class ReftableBatchRefUpdate extends BatchRefUpdate {
}
}
+ // Extracts and peels the refs out of the ReceiveCommands
private static List<Ref> toNewRefs(RevWalk rw, List<ReceiveCommand> pending)
- throws IOException {
+ throws IOException {
List<Ref> refs = new ArrayList<>(pending.size());
for (ReceiveCommand cmd : pending) {
+ if (cmd.getResult() != NOT_ATTEMPTED) {
+ continue;
+ }
+
String name = cmd.getRefName();
ObjectId newId = cmd.getNewId();
String newSymref = cmd.getNewSymref();
if (AnyObjectId.isEqual(ObjectId.zeroId(), newId)
- && newSymref == null) {
+ && newSymref == null) {
refs.add(new ObjectIdRef.Unpeeled(NEW, name, null));
continue;
} else if (newSymref != null) {
refs.add(new SymbolicRef(name,
- new ObjectIdRef.Unpeeled(NEW, newSymref, null)));
+ new ObjectIdRef.Unpeeled(NEW, newSymref, null)));
continue;
}
@@ -385,76 +405,11 @@ public class ReftableBatchRefUpdate extends BatchRefUpdate {
}
if (peel != null) {
refs.add(new ObjectIdRef.PeeledTag(PACKED, name, newId,
- peel.copy()));
+ peel.copy()));
} else {
refs.add(new ObjectIdRef.PeeledNonTag(PACKED, name, newId));
}
}
return refs;
}
-
- private long nextUpdateIndex() throws IOException {
- long updateIndex = 0;
- for (Reftable r : refdb.stack().readers()) {
- if (r instanceof ReftableReader) {
- updateIndex = Math.max(updateIndex,
- ((ReftableReader) r).maxUpdateIndex());
- }
- }
- return updateIndex + 1;
- }
-
- private boolean canCompactTopOfStack(ReftableConfig cfg)
- throws IOException {
- ReftableStack stack = refdb.stack();
- List<Reftable> readers = stack.readers();
- if (readers.isEmpty()) {
- return false;
- }
-
- int lastIdx = readers.size() - 1;
- DfsReftable last = stack.files().get(lastIdx);
- DfsPackDescription desc = last.getPackDescription();
- if (desc.getPackSource() != PackSource.INSERT
- || !packOnlyContainsReftable(desc)) {
- return false;
- }
-
- Reftable table = readers.get(lastIdx);
- int bs = cfg.getRefBlockSize();
- return table instanceof ReftableReader
- && ((ReftableReader) table).size() <= 3 * bs;
- }
-
- private ReftableWriter.Stats compactTopOfStack(OutputStream out,
- ReftableConfig cfg, byte[] newTable) throws IOException {
- List<Reftable> stack = refdb.stack().readers();
- Reftable last = stack.get(stack.size() - 1);
-
- List<Reftable> tables = new ArrayList<>(2);
- tables.add(last);
- tables.add(new ReftableReader(BlockSource.from(newTable)));
-
- ReftableCompactor compactor = new ReftableCompactor();
- compactor.setConfig(cfg);
- compactor.setIncludeDeletes(true);
- compactor.addAll(tables);
- compactor.compact(out);
- return compactor.getStats();
- }
-
- private Set<DfsPackDescription> toPruneTopOfStack() throws IOException {
- List<DfsReftable> stack = refdb.stack().files();
- DfsReftable last = stack.get(stack.size() - 1);
- return Collections.singleton(last.getPackDescription());
- }
-
- private boolean packOnlyContainsReftable(DfsPackDescription desc) {
- for (PackExt ext : PackExt.values()) {
- if (ext != REFTABLE && desc.hasFileExt(ext)) {
- return false;
- }
- }
- return true;
- }
}
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 c4e8f69fa4..a586d42de6 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
@@ -61,45 +61,42 @@ 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 #setMinUpdateIndex(long)} and {@link #setMaxUpdateIndex(long)} are
+ * {@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 #setOldestReflogTimeMillis(long)}, or disable the log altogether with
+ * {@link #setReflogExpireOldestReflogTimeMillis(long)}, or disable the log altogether with
* {@code setOldestReflogTimeMillis(Long.MAX_VALUE)}.
*/
public class ReftableCompactor {
- private final ReftableWriter writer = new ReftableWriter();
- private final ArrayDeque<Reftable> tables = new ArrayDeque<>();
+ private final ReftableWriter writer;
+ private final ArrayDeque<ReftableReader> tables = new ArrayDeque<>();
- private long compactBytesLimit;
- private long bytesToCompact;
private boolean includeDeletes;
- private long minUpdateIndex = -1;
- private long maxUpdateIndex;
- private long oldestReflogTimeMillis;
+ private long reflogExpireMinUpdateIndex = 0;
+ private long reflogExpireMaxUpdateIndex = Long.MAX_VALUE;
+ private long reflogExpireOldestReflogTimeMillis;
private Stats stats;
/**
- * Set configuration for the reftable.
+ * Creates a new compactor.
*
- * @param cfg
- * configuration for the reftable.
- * @return {@code this}
+ * @param out
+ * stream to write the compacted tables to. Caller is responsible
+ * for closing {@code out}.
*/
- public ReftableCompactor setConfig(ReftableConfig cfg) {
- writer.setConfig(cfg);
- return this;
+ public ReftableCompactor(OutputStream out) {
+ writer = new ReftableWriter(out);
}
/**
- * Set limit on number of bytes from source tables to compact.
+ * Set configuration for the reftable.
*
- * @param bytes
- * limit on number of bytes from source tables to compact.
+ * @param cfg
+ * configuration for the reftable.
* @return {@code this}
*/
- public ReftableCompactor setCompactBytesLimit(long bytes) {
- compactBytesLimit = bytes;
+ public ReftableCompactor setConfig(ReftableConfig cfg) {
+ writer.setConfig(cfg);
return this;
}
@@ -128,8 +125,8 @@ public class ReftableCompactor {
* in a stack.
* @return {@code this}
*/
- public ReftableCompactor setMinUpdateIndex(long min) {
- minUpdateIndex = min;
+ public ReftableCompactor setReflogExpireMinUpdateIndex(long min) {
+ reflogExpireMinUpdateIndex = min;
return this;
}
@@ -144,8 +141,8 @@ public class ReftableCompactor {
* used in a stack.
* @return {@code this}
*/
- public ReftableCompactor setMaxUpdateIndex(long max) {
- maxUpdateIndex = max;
+ public ReftableCompactor setReflogExpireMaxUpdateIndex(long max) {
+ reflogExpireMaxUpdateIndex = max;
return this;
}
@@ -159,16 +156,13 @@ public class ReftableCompactor {
* Specified in Java standard milliseconds since the epoch.
* @return {@code this}
*/
- public ReftableCompactor setOldestReflogTimeMillis(long timeMillis) {
- oldestReflogTimeMillis = timeMillis;
+ public ReftableCompactor setReflogExpireOldestReflogTimeMillis(long timeMillis) {
+ reflogExpireOldestReflogTimeMillis = timeMillis;
return this;
}
/**
* Add all of the tables, in the specified order.
- * <p>
- * Unconditionally adds all tables, ignoring the
- * {@link #setCompactBytesLimit(long)}.
*
* @param readers
* tables to compact. Tables should be ordered oldest first/most
@@ -177,67 +171,26 @@ public class ReftableCompactor {
* @throws java.io.IOException
* update indexes of a reader cannot be accessed.
*/
- public void addAll(List<? extends Reftable> readers) throws IOException {
- tables.addAll(readers);
- for (Reftable r : readers) {
- if (r instanceof ReftableReader) {
- adjustUpdateIndexes((ReftableReader) r);
- }
+ public void addAll(List<ReftableReader> readers) throws IOException {
+ for (ReftableReader r : readers) {
+ tables.add(r);
}
}
/**
- * Try to add this reader at the bottom of the stack.
- * <p>
- * A reader may be rejected by returning {@code false} if the compactor is
- * already rewriting its {@link #setCompactBytesLimit(long)}. When this
- * happens the caller should stop trying to add tables, and execute the
- * compaction.
- *
- * @param reader
- * the reader to insert at the bottom of the stack. Caller is
- * responsible for closing the reader.
- * @return {@code true} if the compactor accepted this table; {@code false}
- * if the compactor has reached its limit.
- * @throws java.io.IOException
- * if size of {@code reader}, or its update indexes cannot be read.
- */
- public boolean tryAddFirst(ReftableReader reader) throws IOException {
- long sz = reader.size();
- if (compactBytesLimit > 0 && bytesToCompact + sz > compactBytesLimit) {
- return false;
- }
- bytesToCompact += sz;
- adjustUpdateIndexes(reader);
- tables.addFirst(reader);
- return true;
- }
-
- private void adjustUpdateIndexes(ReftableReader reader) throws IOException {
- if (minUpdateIndex == -1) {
- minUpdateIndex = reader.minUpdateIndex();
- } else {
- minUpdateIndex = Math.min(minUpdateIndex, reader.minUpdateIndex());
- }
- maxUpdateIndex = Math.max(maxUpdateIndex, reader.maxUpdateIndex());
- }
-
- /**
* Write a compaction to {@code out}.
*
- * @param out
- * stream to write the compacted tables to. Caller is responsible
- * for closing {@code out}.
* @throws java.io.IOException
* if tables cannot be read, or cannot be written.
*/
- public void compact(OutputStream out) throws IOException {
+ public void compact() throws IOException {
MergedReftable mr = new MergedReftable(new ArrayList<>(tables));
mr.setIncludeDeletes(includeDeletes);
- writer.setMinUpdateIndex(Math.max(minUpdateIndex, 0));
- writer.setMaxUpdateIndex(maxUpdateIndex);
- writer.begin(out);
+ writer.setMaxUpdateIndex(mr.maxUpdateIndex());
+ writer.setMinUpdateIndex(mr.minUpdateIndex());
+
+ writer.begin();
mergeRefs(mr);
mergeLogs(mr);
writer.finish();
@@ -262,16 +215,14 @@ public class ReftableCompactor {
}
private void mergeLogs(MergedReftable mr) throws IOException {
- if (oldestReflogTimeMillis == Long.MAX_VALUE) {
+ if (reflogExpireOldestReflogTimeMillis == Long.MAX_VALUE) {
return;
}
try (LogCursor lc = mr.allLogs()) {
while (lc.next()) {
long updateIndex = lc.getUpdateIndex();
- if (updateIndex < minUpdateIndex
- || updateIndex > maxUpdateIndex) {
- // Cannot merge log records outside the header's range.
+ if (updateIndex > reflogExpireMaxUpdateIndex || updateIndex < reflogExpireMinUpdateIndex) {
continue;
}
@@ -285,7 +236,7 @@ public class ReftableCompactor {
}
PersonIdent who = log.getWho();
- if (who.getWhen().getTime() >= oldestReflogTimeMillis) {
+ if (who.getWhen().getTime() >= reflogExpireOldestReflogTimeMillis) {
writer.writeLog(
refName,
updateIndex,
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
new file mode 100644
index 0000000000..696347e8fe
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableDatabase.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2017, Google LLC
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.reftable;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.ReflogReader;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/**
+ * Operations on {@link MergedReftable} that is common to various reftable-using
+ * subclasses of {@link RefDatabase}. See
+ * {@link org.eclipse.jgit.internal.storage.dfs.DfsReftableDatabase} for an
+ * example.
+ */
+public abstract class ReftableDatabase {
+ // Protects mergedTables.
+ private final ReentrantLock lock = new ReentrantLock(true);
+
+ private Reftable mergedTables;
+
+ /**
+ * ReftableDatabase lazily initializes its merged reftable on the first read after
+ * construction or clearCache() call. This function should always instantiate a new
+ * MergedReftable based on the list of reftables specified by the underlying storage.
+ *
+ * @return the ReftableStack for this instance
+ * @throws IOException
+ * on I/O problems.
+ */
+ protected abstract MergedReftable openMergedReftable() throws IOException;
+
+ /**
+ * @return the next available logical timestamp for an additional reftable
+ * in the stack.
+ * @throws java.io.IOException
+ * on I/O problems.
+ */
+ public long nextUpdateIndex() throws IOException {
+ lock.lock();
+ try {
+ return reader().maxUpdateIndex() + 1;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @return a ReflogReader for the given ref
+ * @param refname
+ * the name of the ref.
+ * @throws IOException
+ * on I/O problems
+ */
+ public ReflogReader getReflogReader(String refname) throws IOException {
+ lock.lock();
+ try {
+ return new ReftableReflogReader(lock, reader(), refname);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * @return a ReceiveCommand for the change from oldRef to newRef
+ * @param oldRef
+ * a ref
+ * @param newRef
+ * a ref
+ */
+ public static ReceiveCommand toCommand(Ref oldRef, Ref newRef) {
+ ObjectId oldId = toId(oldRef);
+ ObjectId newId = toId(newRef);
+ String name = oldRef != null ? oldRef.getName() : newRef.getName();
+
+ if (oldRef != null && oldRef.isSymbolic()) {
+ if (newRef != null) {
+ if (newRef.isSymbolic()) {
+ return ReceiveCommand.link(oldRef.getTarget().getName(),
+ newRef.getTarget().getName(), name);
+ }
+ // This should pass in oldId for compat with
+ // RefDirectoryUpdate
+ return ReceiveCommand.unlink(oldRef.getTarget().getName(),
+ newId, name);
+ }
+ return ReceiveCommand.unlink(oldRef.getTarget().getName(),
+ ObjectId.zeroId(), name);
+ }
+
+ if (newRef != null && newRef.isSymbolic()) {
+ if (oldRef != null) {
+ if (oldRef.isSymbolic()) {
+ return ReceiveCommand.link(oldRef.getTarget().getName(),
+ newRef.getTarget().getName(), name);
+ }
+ return ReceiveCommand.link(oldId,
+ newRef.getTarget().getName(), name);
+ }
+ return ReceiveCommand.link(ObjectId.zeroId(),
+ newRef.getTarget().getName(), name);
+ }
+
+ return new ReceiveCommand(oldId, newId, name);
+ }
+
+ private static ObjectId toId(Ref ref) {
+ if (ref != null) {
+ ObjectId id = ref.getObjectId();
+ if (id != null) {
+ return id;
+ }
+ }
+ return ObjectId.zeroId();
+ }
+
+ /**
+ * @return the lock protecting underlying ReftableReaders against concurrent
+ * reads.
+ */
+ public ReentrantLock getLock() {
+ return lock;
+ }
+
+ /**
+ * @return the merged reftable that is implemented by the stack of
+ * reftables. Return value must be accessed under lock.
+ * @throws IOException
+ * on I/O problems
+ */
+ private Reftable reader() throws IOException {
+ if (!lock.isLocked()) {
+ throw new IllegalStateException(
+ "must hold lock to access merged table"); //$NON-NLS-1$
+ }
+ if (mergedTables == null) {
+ mergedTables = openMergedReftable();
+ }
+ return mergedTables;
+ }
+
+ /**
+ * @return whether the given refName would be illegal in a repository that
+ * uses loose refs.
+ * @param refName
+ * the name to check
+ * @param added
+ * a sorted set of refs we pretend have been added to the
+ * database.
+ * @param deleted
+ * a set of refs we pretend have been removed from the database.
+ * @throws IOException
+ * on I/O problems
+ */
+ public boolean isNameConflicting(String refName, TreeSet<String> added,
+ Set<String> deleted) throws IOException {
+ lock.lock();
+ try {
+ Reftable table = reader();
+
+ // Cannot be nested within an existing reference.
+ int lastSlash = refName.lastIndexOf('/');
+ while (0 < lastSlash) {
+ String prefix = refName.substring(0, lastSlash);
+ if (!deleted.contains(prefix)
+ && (table.hasRef(prefix) || added.contains(prefix))) {
+ return true;
+ }
+ lastSlash = refName.lastIndexOf('/', lastSlash - 1);
+ }
+
+ // Cannot be the container of an existing reference.
+ String prefix = refName + '/';
+ RefCursor c = table.seekRefsWithPrefix(prefix);
+ while (c.next()) {
+ if (!deleted.contains(c.getRef().getName())) {
+ return true;
+ }
+ }
+
+ String it = added.ceiling(refName + '/');
+ if (it != null && it.startsWith(prefix)) {
+ return true;
+ }
+ return false;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Read a single reference.
+ * <p>
+ * This method expects an unshortened reference name and does not search
+ * using the standard search path.
+ *
+ * @param name
+ * the unabbreviated name of the reference.
+ * @return the reference (if it exists); else {@code null}.
+ * @throws java.io.IOException
+ * the reference space cannot be accessed.
+ */
+ @Nullable
+ public Ref exactRef(String name) throws IOException {
+ lock.lock();
+ try {
+ Reftable table = reader();
+ Ref ref = table.exactRef(name);
+ if (ref != null && ref.isSymbolic()) {
+ return table.resolve(ref);
+ }
+ return ref;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Returns refs whose names start with a given prefix.
+ *
+ * @param prefix
+ * string that names of refs should start with; may be empty (to
+ * return all refs).
+ * @return immutable list of refs whose names start with {@code prefix}.
+ * @throws java.io.IOException
+ * the reference space cannot be accessed.
+ */
+ public List<Ref> getRefsByPrefix(String prefix) throws IOException {
+ List<Ref> all = new ArrayList<>();
+ lock.lock();
+ try {
+ Reftable table = reader();
+ try (RefCursor rc = RefDatabase.ALL.equals(prefix) ? table.allRefs()
+ : table.seekRefsWithPrefix(prefix)) {
+ while (rc.next()) {
+ Ref ref = table.resolve(rc.getRef());
+ if (ref != null && ref.getObjectId() != null) {
+ all.add(ref);
+ }
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+
+ return Collections.unmodifiableList(all);
+ }
+
+ /**
+ * @return whether there is a fast SHA1 to ref map.
+ * @throws IOException in case of I/O problems.
+ */
+ public boolean hasFastTipsWithSha1() throws IOException {
+ lock.lock();
+ try {
+ return reader().hasObjectMap();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Returns all refs that resolve directly to the given {@link ObjectId}.
+ * Includes peeled {@linkObjectId}s.
+ *
+ * @param id
+ * {@link ObjectId} to resolve
+ * @return a {@link Set} of {@link Ref}s whose tips point to the provided
+ * id.
+ * @throws java.io.IOException
+ * on I/O errors.
+ */
+ public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException {
+ lock.lock();
+ try {
+ RefCursor cursor = reader().byObjectId(id);
+ Set<Ref> refs = new HashSet<>();
+ while (cursor.next()) {
+ refs.add(cursor.getRef());
+ }
+ return refs;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Drops all data that might be cached in memory.
+ */
+ public void clearCache() {
+ lock.lock();
+ try {
+ mergedTables = null;
+ } finally {
+ lock.unlock();
+ }
+ }
+}
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 4f0ff2d1d1..3b912ea1c2 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
@@ -78,7 +78,7 @@ import org.eclipse.jgit.util.NB;
* {@code ReftableReader} is not thread-safe. Concurrent readers need their own
* instance to read from the same file.
*/
-public class ReftableReader extends Reftable {
+public class ReftableReader extends Reftable implements AutoCloseable {
private final BlockSource src;
private int blockSize = -1;
@@ -128,16 +128,20 @@ public class ReftableReader extends Reftable {
return blockSize;
}
+ @Override
+ public boolean hasObjectMap() throws IOException {
+ if (objIndexPosition == -1) {
+ readFileFooter();
+ }
+
+ // We have the map, we have no refs, or the table is small.
+ return (objPosition > 0 || refEnd == 24 || refIndexPosition == 0);
+ }
+
/**
- * Get the minimum update index for log entries that appear in this
- * reftable.
- *
- * @return the minimum update index for log entries that appear in this
- * reftable. This should be 1 higher than the prior reftable's
- * {@code maxUpdateIndex} if this table is used in a stack.
- * @throws java.io.IOException
- * file cannot be read.
+ * {@inheritDoc}
*/
+ @Override
public long minUpdateIndex() throws IOException {
if (blockSize == -1) {
readFileHeader();
@@ -146,15 +150,9 @@ public class ReftableReader extends Reftable {
}
/**
- * Get the maximum update index for log entries that appear in this
- * reftable.
- *
- * @return the maximum update index for log entries that appear in this
- * reftable. This should be 1 higher than the prior reftable's
- * {@code maxUpdateIndex} if this table is used in a stack.
- * @throws java.io.IOException
- * file cannot be read.
+ * {@inheritDoc}
*/
+ @Override
public long maxUpdateIndex() throws IOException {
if (blockSize == -1) {
readFileHeader();
@@ -169,11 +167,13 @@ public class ReftableReader extends Reftable {
readFileHeader();
}
- long end = refEnd > 0 ? refEnd : (src.size() - FILE_FOOTER_LEN);
- src.adviseSequentialRead(0, end);
+ if (refEnd == 0) {
+ readFileFooter();
+ }
+ src.adviseSequentialRead(0, refEnd);
- RefCursorImpl i = new RefCursorImpl(end, null, false);
- i.block = readBlock(0, end);
+ RefCursorImpl i = new RefCursorImpl(refEnd, null, false);
+ i.block = readBlock(0, refEnd);
return i;
}
@@ -468,7 +468,7 @@ public class ReftableReader extends Reftable {
BlockReader b = new BlockReader();
b.readBlock(src, pos, sz);
- if (b.type() == INDEX_BLOCK_TYPE && !b.truncated()) {
+ if (b.type() == INDEX_BLOCK_TYPE) {
if (indexCache == null) {
indexCache = new LongMap<>();
}
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
new file mode 100644
index 0000000000..c75d3cfa55
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReflogReader.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2019, Google LLC
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.internal.storage.reftable;
+
+import org.eclipse.jgit.lib.ReflogEntry;
+import org.eclipse.jgit.lib.ReflogReader;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
+
+/**
+ * Implement the ReflogReader interface for a reflog stored in reftable.
+ */
+public class ReftableReflogReader implements ReflogReader {
+ private final Lock lock;
+
+ private final Reftable reftable;
+
+ private final String refname;
+
+ ReftableReflogReader(Lock lock, Reftable merged, String refname) {
+ this.lock = lock;
+ this.reftable = merged;
+ this.refname = refname;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ReflogEntry getLastEntry() throws IOException {
+ lock.lock();
+ try {
+ LogCursor cursor = reftable.seekLog(refname);
+ return cursor.next() ? cursor.getReflogEntry() : null;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public List<ReflogEntry> getReverseEntries() throws IOException {
+ return getReverseEntries(Integer.MAX_VALUE);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ReflogEntry getReverseEntry(int number) throws IOException {
+ lock.lock();
+ try {
+ LogCursor cursor = reftable.seekLog(refname);
+ while (true) {
+ if (!cursor.next() || number < 0) {
+ return null;
+ }
+ if (number == 0) {
+ return cursor.getReflogEntry();
+ }
+ number--;
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public List<ReflogEntry> getReverseEntries(int max) throws IOException {
+ lock.lock();
+ try {
+ LogCursor cursor = reftable.seekLog(refname);
+
+ List<ReflogEntry> result = new ArrayList<>();
+ while (cursor.next() && result.size() < max) {
+ result.add(cursor.getReflogEntry());
+ }
+
+ return result;
+ } finally {
+ lock.unlock();
+ }
+ }
+}
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 6459c2763d..96f8cb163a 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
@@ -108,6 +108,7 @@ public class ReftableWriter {
private long minUpdateIndex;
private long maxUpdateIndex;
+ private OutputStream outputStream;
private ReftableOutputStream out;
private ObjectIdSubclassMap<RefList> obj2ref;
@@ -122,21 +123,27 @@ public class ReftableWriter {
/**
* Initialize a writer with a default configuration.
+ *
+ * @param os
+ * output stream.
*/
- public ReftableWriter() {
- this(new ReftableConfig());
+ public ReftableWriter(OutputStream os) {
+ this(new ReftableConfig(), os);
lastRef = null;
lastLog = null;
}
/**
- * Initialize a writer with a specific configuration.
+ * Initialize a writer with a configuration.
*
* @param cfg
- * configuration for the writer.
+ * configuration for the writer
+ * @param os
+ * output stream.
*/
- public ReftableWriter(ReftableConfig cfg) {
+ public ReftableWriter(ReftableConfig cfg, OutputStream os) {
config = cfg;
+ outputStream = os;
}
/**
@@ -183,16 +190,16 @@ public class ReftableWriter {
}
/**
- * Begin writing the reftable.
+ * Begin writing the reftable. Should be called only once. Call this
+ * if a stream was passed to the constructor.
*
- * @param os
- * stream to write the table to. Caller is responsible for
- * closing the stream after invoking {@link #finish()}.
* @return {@code this}
- * @throws java.io.IOException
- * if reftable header cannot be written.
*/
- public ReftableWriter begin(OutputStream os) throws IOException {
+ public ReftableWriter begin() {
+ if (out != null) {
+ throw new IllegalStateException("begin() called twice.");//$NON-NLS-1$
+ }
+
refBlockSize = config.getRefBlockSize();
logBlockSize = config.getLogBlockSize();
restartInterval = config.getRestartInterval();
@@ -212,7 +219,7 @@ public class ReftableWriter {
restartInterval = refBlockSize < (60 << 10) ? 16 : 64;
}
- out = new ReftableOutputStream(os, refBlockSize, alignBlocks);
+ out = new ReftableOutputStream(outputStream, refBlockSize, alignBlocks);
refs = new Section(REF_BLOCK_TYPE);
if (indexObjects) {
obj2ref = new ObjectIdSubclassMap<>();
@@ -223,6 +230,7 @@ public class ReftableWriter {
/**
* Sort a collection of references and write them to the reftable.
+ * The input refs may not have duplicate names.
*
* @param refsToPack
* references to sort and write.
@@ -236,10 +244,16 @@ public class ReftableWriter {
.map(r -> new RefEntry(r, maxUpdateIndex - minUpdateIndex))
.sorted(Entry::compare)
.iterator();
+ RefEntry last = null;
while (itr.hasNext()) {
RefEntry entry = itr.next();
+ if (last != null && Entry.compare(last, entry) == 0) {
+ throwIllegalEntry(last, entry);
+ }
+
long blockPos = refs.write(entry);
indexRef(entry.ref, blockPos);
+ last = entry;
}
return this;
}
@@ -288,7 +302,7 @@ public class ReftableWriter {
private void throwIllegalEntry(Entry last, Entry now) {
throw new IllegalArgumentException(MessageFormat.format(
- JGitText.get().refTableRecordsMustIncrease,
+ JGitText.get().reftableRecordsMustIncrease,
new String(last.key, UTF_8), new String(now.key, UTF_8)));
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java
index da98e3fadd..9c5423fb0e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java
@@ -102,9 +102,8 @@ class RefTreeBatch extends BatchRefUpdate {
if (isAtomic()) {
ReceiveCommand.abort(getCommands());
return;
- } else {
- continue;
}
+ continue;
}
}
todo.add(new Command(rw, c));
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 882b2d055b..39a67afae3 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
@@ -82,9 +82,8 @@ public class NetscapeCookieFileCache {
public static NetscapeCookieFileCache getInstance(HttpConfig config) {
if (instance == null) {
return new NetscapeCookieFileCache(config);
- } else {
- return instance;
}
+ return instance;
}
/**
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 c1e94a0a3e..ee6adeee98 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
@@ -384,9 +384,8 @@ public class OpenSshConfigFile {
private static boolean isHostMatch(String pattern, String name) {
if (pattern.startsWith("!")) { //$NON-NLS-1$
return !patternMatchesHost(pattern.substring(1), name);
- } else {
- return patternMatchesHost(pattern, name);
}
+ return patternMatchesHost(pattern, name);
}
private static boolean patternMatchesHost(String pattern, String name) {
@@ -399,10 +398,9 @@ public class OpenSshConfigFile {
}
fn.append(name);
return fn.isMatch();
- } else {
- // Not a pattern but a full host name
- return pattern.equals(name);
}
+ // Not a pattern but a full host name
+ return pattern.equals(name);
}
private static String dequote(String value) {
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 4f90e69008..24850ee44c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AnyObjectId.java
@@ -302,10 +302,10 @@ public abstract class AnyObjectId implements Comparable<AnyObjectId> {
/** {@inheritDoc} */
@Override
public final boolean equals(Object o) {
- if (o instanceof AnyObjectId)
+ if (o instanceof AnyObjectId) {
return equals((AnyObjectId) o);
- else
- return false;
+ }
+ return false;
}
/**
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 96e50667b3..98a46f3e54 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
@@ -103,25 +103,29 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
private static File getSymRef(File workTree, File dotGit, FS fs)
throws IOException {
byte[] content = IO.readFully(dotGit);
- if (!isSymRef(content))
+ if (!isSymRef(content)) {
throw new IOException(MessageFormat.format(
JGitText.get().invalidGitdirRef, dotGit.getAbsolutePath()));
+ }
int pathStart = 8;
int lineEnd = RawParseUtils.nextLF(content, pathStart);
while (content[lineEnd - 1] == '\n' ||
- (content[lineEnd - 1] == '\r' && SystemReader.getInstance().isWindows()))
+ (content[lineEnd - 1] == '\r'
+ && SystemReader.getInstance().isWindows())) {
lineEnd--;
- if (lineEnd == pathStart)
+ }
+ if (lineEnd == pathStart) {
throw new IOException(MessageFormat.format(
JGitText.get().invalidGitdirRef, dotGit.getAbsolutePath()));
+ }
String gitdirPath = RawParseUtils.decode(content, pathStart, lineEnd);
File gitdirFile = fs.resolve(workTree, gitdirPath);
- if (gitdirFile.isAbsolute())
+ if (gitdirFile.isAbsolute()) {
return gitdirFile;
- else
- return new File(workTree, gitdirPath).getCanonicalFile();
+ }
+ return new File(workTree, gitdirPath).getCanonicalFile();
}
private FS fs;
@@ -723,9 +727,8 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
.getAbsolutePath(), err.getMessage()));
}
return cfg;
- } else {
- return new Config();
}
+ return new Config();
}
private File guessWorkTreeOrFail() throws IOException {
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 be53c4b4f6..cad747bcff 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BranchConfig.java
@@ -232,9 +232,9 @@ public class BranchConfig {
private String getRemoteOrDefault() {
String remote = getRemote();
- if (remote == null)
+ if (remote == null) {
return Constants.DEFAULT_REMOTE_NAME;
- else
- return remote;
+ }
+ return remote;
}
}
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 d26b7fd17d..2ef365399f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java
@@ -1407,12 +1407,11 @@ public class Config {
}
trailingSpaces.append(cc);
continue;
- } else {
- inLeadingSpace = false;
- if (trailingSpaces != null) {
- value.append(trailingSpaces);
- trailingSpaces.setLength(0);
- }
+ }
+ inLeadingSpace = false;
+ if (trailingSpaces != null) {
+ value.append(trailingSpaces);
+ trailingSpaces.setLength(0);
}
if ('\\' == c) {
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 078bf786f2..99512a6a4a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -149,6 +149,18 @@ public final class ConfigConstants {
*/
public static final String CONFIG_KEY_GPGSIGN = "gpgSign";
+ /**
+ * The "hooksPath" key.
+ * @since 5.6
+ */
+ public static final String CONFIG_KEY_HOOKS_PATH = "hooksPath";
+
+ /**
+ * The "quotePath" key.
+ * @since 5.6
+ */
+ public static final String CONFIG_KEY_QUOTE_PATH = "quotePath";
+
/** The "algorithm" key */
public static final String CONFIG_KEY_ALGORITHM = "algorithm";
@@ -432,6 +444,12 @@ public final class ConfigConstants {
public static final String CONFIG_RENAMELIMIT_COPIES = "copies";
/**
+ * A "refStorage" value in the "extensions".
+ * @since 5.6.2
+ */
+ public static final String CONFIG_REF_STORAGE_REFTABLE = "reftable";
+
+ /**
* The "renames" key in the "diff" section
* @since 3.0
*/
@@ -526,9 +544,24 @@ public final class ConfigConstants {
*/
public static final String CONFIG_KEY_MIN_RACY_THRESHOLD = "minRacyThreshold";
+
+ /**
+ * The "refStorage" key
+ *
+ * @since 5.6.2
+ */
+ public static final String CONFIG_KEY_REF_STORAGE = "refStorage";
+
/**
* The "jmx" section
* @since 5.1.13
*/
public static final String CONFIG_JMX_SECTION = "jmx";
+
+ /**
+ * The "extensions" section
+ *
+ * @since 5.6.2
+ */
+ public static final String CONFIG_EXTENSIONS_SECTION = "extensions";
}
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 9274fc6777..3c05cab862 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -281,6 +281,18 @@ public final class Constants {
*/
public static final String OBJECTS = "objects";
+ /**
+ * Reftable folder name
+ * @since 5.6
+ */
+ public static final String REFTABLE = "reftable";
+
+ /**
+ * Reftable table list name.
+ * @since 5.6.2
+ */
+ public static final String TABLES_LIST = "tables.list";
+
/** Info refs folder */
public static final String INFO_REFS = "info/refs";
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 98de3a91cc..0056ad7632 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java
@@ -60,7 +60,7 @@ public class CoreConfig {
public static final Config.SectionParser<CoreConfig> KEY = CoreConfig::new;
/** Permissible values for {@code core.autocrlf}. */
- public static enum AutoCRLF {
+ public enum AutoCRLF {
/** Automatic CRLF-&gt;LF conversion is disabled. */
FALSE,
@@ -78,45 +78,45 @@ public class CoreConfig {
*
* @since 4.3
*/
- public static enum EOL {
- /** checkin with LF, checkout with CRLF. */
+ public enum EOL {
+ /** Check in with LF, check out with CRLF. */
CRLF,
- /** checkin with LF, checkout without conversion. */
+ /** Check in with LF, check out without conversion. */
LF,
- /** use the platform's native line ending. */
+ /** Use the platform's native line ending. */
NATIVE;
}
/**
- * EOL stream conversion protocol
+ * EOL stream conversion protocol.
*
* @since 4.3
*/
- public static enum EolStreamType {
- /** convert to CRLF without binary detection */
+ public enum EolStreamType {
+ /** Convert to CRLF without binary detection. */
TEXT_CRLF,
- /** convert to LF without binary detection */
+ /** Convert to LF without binary detection. */
TEXT_LF,
- /** convert to CRLF with binary detection */
+ /** Convert to CRLF with binary detection. */
AUTO_CRLF,
- /** convert to LF with binary detection */
+ /** Convert to LF with binary detection. */
AUTO_LF,
- /** do not convert */
+ /** Do not convert. */
DIRECT;
}
/**
- * Permissible values for {@code core.checkstat}
+ * Permissible values for {@code core.checkstat}.
*
* @since 3.0
*/
- public static enum CheckStat {
+ public enum CheckStat {
/**
* Only check the size and whole second part of time stamp when
* comparing the stat info in the dircache with actual file stat info.
@@ -130,11 +130,30 @@ public class CoreConfig {
DEFAULT
}
+ /**
+ * Permissible values for {@code core.logAllRefUpdates}.
+ *
+ * @since 5.6
+ */
+ public enum LogRefUpdates {
+ /** Don't create ref logs; default for bare repositories. */
+ FALSE,
+
+ /**
+ * Create ref logs for refs/heads/**, refs/remotes/**, refs/notes/**,
+ * and for HEAD. Default for non-bare repositories.
+ */
+ TRUE,
+
+ /** Create ref logs for all refs/** and for HEAD. */
+ ALWAYS
+ }
+
private final int compression;
private final int packIndexVersion;
- private final boolean logAllRefUpdates;
+ private final LogRefUpdates logAllRefUpdates;
private final String excludesfile;
@@ -145,24 +164,27 @@ public class CoreConfig {
*
* @since 3.3
*/
- public static enum SymLinks {
- /** Checkout symbolic links as plain files */
+ public enum SymLinks {
+ /** Check out symbolic links as plain files . */
FALSE,
- /** Checkout symbolic links as links */
+
+ /** Check out symbolic links as links. */
TRUE
}
/**
- * Options for hiding files whose names start with a period
+ * Options for hiding files whose names start with a period.
*
* @since 3.5
*/
- public static enum HideDotFiles {
- /** Do not hide .files */
+ public enum HideDotFiles {
+ /** Do not hide .files. */
FALSE,
- /** Hide add .files */
+
+ /** Hide add .files. */
TRUE,
- /** Hide only .git */
+
+ /** Hide only .git. */
DOTGITONLY
}
@@ -171,8 +193,9 @@ public class CoreConfig {
ConfigConstants.CONFIG_KEY_COMPRESSION, DEFAULT_COMPRESSION);
packIndexVersion = rc.getInt(ConfigConstants.CONFIG_PACK_SECTION,
ConfigConstants.CONFIG_KEY_INDEXVERSION, 2);
- logAllRefUpdates = rc.getBoolean(ConfigConstants.CONFIG_CORE_SECTION,
- ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true);
+ 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,
@@ -201,9 +224,14 @@ 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 logAllRefUpdates;
+ return !LogRefUpdates.FALSE.equals(logAllRefUpdates);
}
/**
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 e865da83b1..23e8de0e35 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/DefaultTypedConfigGetter.java
@@ -134,11 +134,9 @@ public class DefaultTypedConfigGetter implements TypedConfigGetter {
throw new IllegalArgumentException(
MessageFormat.format(JGitText.get().enumValueNotSupported3,
section, subsection, name, value));
- } else {
- throw new IllegalArgumentException(
- MessageFormat.format(JGitText.get().enumValueNotSupported2,
- section, name, value));
}
+ throw new IllegalArgumentException(MessageFormat.format(
+ JGitText.get().enumValueNotSupported2, section, name, value));
}
/** {@inheritDoc} */
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 ce1eb597fc..cbb3e54903 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
@@ -108,7 +108,7 @@ public class IndexDiff {
* @see IndexDiff#getConflictingStageStates()
* @since 3.0
*/
- public static enum StageState {
+ public enum StageState {
/**
* Exists in base, but neither in ours nor in theirs.
*/
@@ -243,11 +243,11 @@ public class IndexDiff {
}
}
- private final static int TREE = 0;
+ private static final int TREE = 0;
- private final static int INDEX = 1;
+ private static final int INDEX = 1;
- private final static int WORKDIR = 2;
+ private static final int WORKDIR = 2;
private final Repository repository;
@@ -384,7 +384,32 @@ public class IndexDiff {
* @throws java.io.IOException
*/
public boolean diff() throws IOException {
- return diff(null, 0, 0, ""); //$NON-NLS-1$
+ return diff(null);
+ }
+
+ /**
+ * Run the diff operation. Until this is called, all lists will be empty.
+ * Use
+ * {@link #diff(ProgressMonitor, int, int, String, RepositoryBuilderFactory)}
+ * if a progress monitor is required.
+ * <p>
+ * The operation may create repositories for submodules using builders
+ * provided by the given {@code factory}, if any, and will also close these
+ * submodule repositories again.
+ * </p>
+ *
+ * @param factory
+ * the {@link RepositoryBuilderFactory} to use to create builders
+ * to create submodule repositories, if needed; if {@code null},
+ * submodule repositories will be built using a plain
+ * {@link RepositoryBuilder}.
+ * @return if anything is different between index, tree, and workdir
+ * @throws java.io.IOException
+ * @since 5.6
+ */
+ public boolean diff(RepositoryBuilderFactory factory)
+ throws IOException {
+ return diff(null, 0, 0, "", factory); //$NON-NLS-1$
}
/**
@@ -410,6 +435,45 @@ public class IndexDiff {
public boolean diff(final ProgressMonitor monitor, int estWorkTreeSize,
int estIndexSize, final String title)
throws IOException {
+ return diff(monitor, estWorkTreeSize, estIndexSize, title, null);
+ }
+
+ /**
+ * Run the diff operation. Until this is called, all lists will be empty.
+ * <p>
+ * The operation may be aborted by the progress monitor. In that event it
+ * will report what was found before the cancel operation was detected.
+ * Callers should ignore the result if monitor.isCancelled() is true. If a
+ * progress monitor is not needed, callers should use {@link #diff()}
+ * instead. Progress reporting is crude and approximate and only intended
+ * for informing the user.
+ * </p>
+ * <p>
+ * The operation may create repositories for submodules using builders
+ * provided by the given {@code factory}, if any, and will also close these
+ * submodule repositories again.
+ * </p>
+ *
+ * @param monitor
+ * for reporting progress, may be null
+ * @param estWorkTreeSize
+ * 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 factory
+ * the {@link RepositoryBuilderFactory} to use to create builders
+ * to create submodule repositories, if needed; if {@code null},
+ * submodule repositories will be built using a plain
+ * {@link RepositoryBuilder}.
+ * @return if anything is different between index, tree, and workdir
+ * @throws java.io.IOException
+ * @since 5.6
+ */
+ public boolean diff(ProgressMonitor monitor, int estWorkTreeSize,
+ int estIndexSize, String title, RepositoryBuilderFactory factory)
+ throws IOException {
dirCache = repository.readDirCache();
try (TreeWalk treeWalk = new TreeWalk(repository)) {
@@ -535,64 +599,69 @@ public class IndexDiff {
}
if (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
- IgnoreSubmoduleMode localIgnoreSubmoduleMode = ignoreSubmoduleMode;
- SubmoduleWalk smw = SubmoduleWalk.forIndex(repository);
- while (smw.next()) {
- try {
- if (localIgnoreSubmoduleMode == null)
- localIgnoreSubmoduleMode = smw.getModulesIgnore();
- if (IgnoreSubmoduleMode.ALL
- .equals(localIgnoreSubmoduleMode))
- continue;
- } catch (ConfigInvalidException e) {
- throw new IOException(MessageFormat.format(
- JGitText.get().invalidIgnoreParamSubmodule,
- smw.getPath()), e);
- }
- try (Repository subRepo = smw.getRepository()) {
- String subRepoPath = smw.getPath();
- if (subRepo != null) {
- ObjectId subHead = subRepo.resolve("HEAD"); //$NON-NLS-1$
- if (subHead != null
- && !subHead.equals(smw.getObjectId())) {
- modified.add(subRepoPath);
- recordFileMode(subRepoPath, FileMode.GITLINK);
- } else if (ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY) {
- IndexDiff smid = submoduleIndexDiffs.get(smw
- .getPath());
- if (smid == null) {
- smid = new IndexDiff(subRepo,
- smw.getObjectId(),
- wTreeIt.getWorkingTreeIterator(subRepo));
- submoduleIndexDiffs.put(subRepoPath, smid);
- }
- if (smid.diff()) {
- if (ignoreSubmoduleMode == IgnoreSubmoduleMode.UNTRACKED
- && smid.getAdded().isEmpty()
- && smid.getChanged().isEmpty()
- && smid.getConflicting().isEmpty()
- && smid.getMissing().isEmpty()
- && smid.getModified().isEmpty()
- && smid.getRemoved().isEmpty()) {
- continue;
- }
+ try (SubmoduleWalk smw = new SubmoduleWalk(repository)) {
+ smw.setTree(new DirCacheIterator(dirCache));
+ smw.setBuilderFactory(factory);
+ while (smw.next()) {
+ IgnoreSubmoduleMode localIgnoreSubmoduleMode = ignoreSubmoduleMode;
+ try {
+ if (localIgnoreSubmoduleMode == null)
+ localIgnoreSubmoduleMode = smw.getModulesIgnore();
+ if (IgnoreSubmoduleMode.ALL
+ .equals(localIgnoreSubmoduleMode))
+ continue;
+ } catch (ConfigInvalidException e) {
+ throw new IOException(MessageFormat.format(
+ JGitText.get().invalidIgnoreParamSubmodule,
+ smw.getPath()), e);
+ }
+ try (Repository subRepo = smw.getRepository()) {
+ String subRepoPath = smw.getPath();
+ if (subRepo != null) {
+ ObjectId subHead = subRepo.resolve("HEAD"); //$NON-NLS-1$
+ if (subHead != null
+ && !subHead.equals(smw.getObjectId())) {
modified.add(subRepoPath);
recordFileMode(subRepoPath, FileMode.GITLINK);
+ } else if (localIgnoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY) {
+ IndexDiff smid = submoduleIndexDiffs
+ .get(smw.getPath());
+ if (smid == null) {
+ smid = new IndexDiff(subRepo,
+ smw.getObjectId(),
+ wTreeIt.getWorkingTreeIterator(
+ subRepo));
+ submoduleIndexDiffs.put(subRepoPath, smid);
+ }
+ if (smid.diff(factory)) {
+ if (localIgnoreSubmoduleMode == IgnoreSubmoduleMode.UNTRACKED
+ && smid.getAdded().isEmpty()
+ && smid.getChanged().isEmpty()
+ && smid.getConflicting().isEmpty()
+ && smid.getMissing().isEmpty()
+ && smid.getModified().isEmpty()
+ && smid.getRemoved().isEmpty()) {
+ continue;
+ }
+ modified.add(subRepoPath);
+ recordFileMode(subRepoPath,
+ FileMode.GITLINK);
+ }
}
- }
- } else if (missingSubmodules.remove(subRepoPath)) {
- // If the directory is there and empty but the submodule
- // repository in .git/modules doesn't exist yet it isn't
- // "missing".
- File gitDir = new File(
- new File(repository.getDirectory(),
- Constants.MODULES),
- subRepoPath);
- if (!gitDir.isDirectory()) {
- File dir = SubmoduleWalk.getSubmoduleDirectory(
- repository, subRepoPath);
- if (dir.isDirectory() && !hasFiles(dir)) {
- missing.remove(subRepoPath);
+ } else if (missingSubmodules.remove(subRepoPath)) {
+ // If the directory is there and empty but the
+ // submodule repository in .git/modules doesn't
+ // exist yet it isn't "missing".
+ File gitDir = new File(
+ new File(repository.getDirectory(),
+ Constants.MODULES),
+ subRepoPath);
+ if (!gitDir.isDirectory()) {
+ File dir = SubmoduleWalk.getSubmoduleDirectory(
+ repository, subRepoPath);
+ if (dir.isDirectory() && !hasFiles(dir)) {
+ missing.remove(subRepoPath);
+ }
}
}
}
@@ -602,16 +671,17 @@ public class IndexDiff {
}
// consume the remaining work
- if (monitor != null)
+ if (monitor != null) {
monitor.endTask();
+ }
ignored = indexDiffFilter.getIgnoredPaths();
if (added.isEmpty() && changed.isEmpty() && removed.isEmpty()
&& missing.isEmpty() && modified.isEmpty()
- && untracked.isEmpty())
+ && untracked.isEmpty()) {
return false;
- else
- return true;
+ }
+ return true;
}
private boolean hasFiles(File directory) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/InflaterCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/InflaterCache.java
index dd0f18e165..314bde2c84 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/InflaterCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/InflaterCache.java
@@ -73,7 +73,7 @@ public class InflaterCache {
return r != null ? r : new Inflater(false);
}
- private synchronized static Inflater getImpl() {
+ private static synchronized Inflater getImpl() {
if (openInflaterCount > 0) {
final Inflater r = inflaterCache[--openInflaterCount];
inflaterCache[openInflaterCount] = null;
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 74c712c46c..8730d66ecc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java
@@ -355,7 +355,7 @@ public class ObjectIdOwnerMap<V extends ObjectIdOwnerMap.Entry>
}
/** Type of entry stored in the {@link ObjectIdOwnerMap}. */
- public static abstract class Entry extends ObjectId {
+ public abstract static class Entry extends ObjectId {
transient Entry next;
/**
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 77fa1b2346..edbb4b1c86 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectInserter.java
@@ -98,7 +98,7 @@ public abstract class ObjectInserter implements AutoCloseable {
}
/** Wraps a delegate ObjectInserter. */
- public static abstract class Filter extends ObjectInserter {
+ public abstract static class Filter extends ObjectInserter {
/** @return delegate ObjectInserter to handle all processing. */
protected abstract ObjectInserter delegate();
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 2e52f031cd..b4734210f7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectLoader.java
@@ -333,7 +333,7 @@ public abstract class ObjectLoader {
*
* @since 4.10
*/
- public static abstract class Filter extends ObjectLoader {
+ public abstract static class Filter extends ObjectLoader {
/**
* @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 700b9dbe85..6d544670b0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java
@@ -293,9 +293,8 @@ public abstract class ObjectReader implements AutoCloseable {
if (idItr.hasNext()) {
cur = idItr.next();
return true;
- } else {
- return false;
}
+ return false;
}
@Override
@@ -383,9 +382,8 @@ public abstract class ObjectReader implements AutoCloseable {
cur = idItr.next();
sz = getObjectSize(cur, OBJ_ANY);
return true;
- } else {
- return false;
}
+ return false;
}
@Override
@@ -497,7 +495,7 @@ public abstract class ObjectReader implements AutoCloseable {
*
* @since 4.4
*/
- public static abstract class Filter extends ObjectReader {
+ public abstract static class Filter extends ObjectReader {
/**
* @return delegate ObjectReader to handle all processing.
* @since 4.4
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 4e235b05c0..f893fd1bf9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoLine.java
@@ -58,7 +58,7 @@ public class RebaseTodoLine {
* Describes rebase actions
*/
@SuppressWarnings("nls")
- public static enum Action {
+ public enum Action {
/** Use commit */
PICK("pick", "p"),
@@ -105,7 +105,7 @@ public class RebaseTodoLine {
* @param token
* @return the Action
*/
- static public Action parse(String token) {
+ public static Action parse(String token) {
for (Action action : Action.values()) {
if (action.token.equals(token)
|| action.shortToken.equals(token))
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 4d9450e758..9b5a1fdc22 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -497,6 +497,20 @@ public abstract class RefDatabase {
}
/**
+ * If the ref database does not support fast inverse queries, it may
+ * be advantageous to build a complete SHA1 to ref map in advance for
+ * multiple uses. To let applications decide on this decision,
+ * this function indicates whether the inverse map is available.
+ *
+ * @return whether this RefDatabase supports fast inverse ref queries.
+ * @throws IOException on I/O problems.
+ * @since 5.6
+ */
+ public boolean hasFastTipsWithSha1() throws IOException {
+ return false;
+ }
+
+ /**
* Check if any refs exist in the ref database.
* <p>
* This uses the same definition of refs as {@link #getRefs()}. In
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 eca15c032a..eb5e139546 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefUpdate.java
@@ -66,7 +66,7 @@ public abstract class RefUpdate {
* unknown values are failures, and may generally treat them the same as
* {@link #REJECTED_OTHER_REASON}.
*/
- public static enum Result {
+ public enum Result {
/** The ref update/delete has not been attempted by the caller. */
NOT_ATTEMPTED,
@@ -827,7 +827,7 @@ public abstract class RefUpdate {
* Handle the abstraction of storing a ref update. This is because both
* updating and deleting of a ref have merge testing in common.
*/
- private static abstract class Store {
+ private abstract static class Store {
abstract Result execute(Result status) 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 68866ea279..0e9cf58cf2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -503,9 +503,8 @@ public abstract class Repository implements AutoCloseable {
if (resolved instanceof String) {
final Ref ref = findRef((String) resolved);
return ref != null ? ref.getLeaf().getObjectId() : null;
- } else {
- return (ObjectId) resolved;
}
+ return (ObjectId) resolved;
}
}
@@ -527,11 +526,12 @@ public abstract class Repository implements AutoCloseable {
try (RevWalk rw = new RevWalk(this)) {
rw.setRetainBody(true);
Object resolved = resolve(rw, revstr);
- if (resolved != null)
- if (resolved instanceof String)
+ if (resolved != null) {
+ if (resolved instanceof String) {
return (String) resolved;
- else
- return ((AnyObjectId) resolved).getName();
+ }
+ return ((AnyObjectId) resolved).getName();
+ }
return null;
}
}
@@ -760,15 +760,15 @@ public abstract class Repository implements AutoCloseable {
if (name == null)
throw new RevisionSyntaxException(revstr);
} else if (time.matches("^-\\d+$")) { //$NON-NLS-1$
- if (name != null)
+ if (name != null) {
throw new RevisionSyntaxException(revstr);
- else {
- String previousCheckout = resolveReflogCheckout(-Integer
- .parseInt(time));
- if (ObjectId.isId(previousCheckout))
- rev = parseSimple(rw, previousCheckout);
- else
- name = previousCheckout;
+ }
+ String previousCheckout = resolveReflogCheckout(
+ -Integer.parseInt(time));
+ if (ObjectId.isId(previousCheckout)) {
+ rev = parseSimple(rw, previousCheckout);
+ } else {
+ name = previousCheckout;
}
} else {
if (name == null)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilderFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilderFactory.java
new file mode 100644
index 0000000000..fc12516837
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryBuilderFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2019, 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
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.lib;
+
+import java.util.function.Supplier;
+
+/**
+ * A factory for {@link BaseRepositoryBuilder}s.
+ * <p>
+ * Note that a {@link BaseRepositoryBuilder} should be used only once to build a
+ * repository. Otherwise subsequently built repositories may be built using
+ * settings made for earlier built repositories.
+ * </p>
+ *
+ * @since 5.6
+ */
+public interface RepositoryBuilderFactory extends
+ Supplier<BaseRepositoryBuilder<? extends BaseRepositoryBuilder, ? extends Repository>> {
+ // Empty
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
index fa113bfc6b..5a6063e9ab 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RepositoryCache.java
@@ -66,7 +66,7 @@ import org.slf4j.LoggerFactory;
* Cache of active {@link org.eclipse.jgit.lib.Repository} instances.
*/
public class RepositoryCache {
- private final static Logger LOG = LoggerFactory
+ private static final Logger LOG = LoggerFactory
.getLogger(RepositoryCache.class);
private static final RepositoryCache cache = new RepositoryCache();
@@ -476,7 +476,8 @@ public class RepositoryCache {
public static boolean isGitRepository(File dir, FS fs) {
return fs.resolve(dir, Constants.OBJECTS).exists()
&& fs.resolve(dir, "refs").exists() //$NON-NLS-1$
- && isValidHead(new File(dir, Constants.HEAD));
+ && (fs.resolve(dir, Constants.REFTABLE).exists()
+ || isValidHead(new File(dir, Constants.HEAD)));
}
private static boolean isValidHead(File head) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java
index 11db7c5998..f28334cb80 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgKeyLocator.java
@@ -72,12 +72,14 @@ import org.bouncycastle.gpg.keybox.PublicKeyRingBlob;
import org.bouncycastle.gpg.keybox.UserID;
import org.bouncycastle.gpg.keybox.jcajce.JcaKeyBoxBuilder;
import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPKeyFlags;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory;
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
@@ -90,6 +92,7 @@ import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.StringUtils;
import org.eclipse.jgit.util.SystemReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -190,19 +193,92 @@ class BouncyCastleGpgKeyLocator {
}
}
- private boolean containsSigningKey(String userId) {
- return userId.toLowerCase(Locale.ROOT)
- .contains(signingKey.toLowerCase(Locale.ROOT));
+ /**
+ * Checks whether a given OpenPGP {@code userId} matches a given
+ * {@code signingKeySpec}, which is supposed to have one of the formats
+ * defined by GPG.
+ * <p>
+ * Not all formats are supported; only formats starting with '=', '&lt;',
+ * '@', and '*' are handled. Any other format results in a case-insensitive
+ * substring match.
+ * </p>
+ *
+ * @param userId
+ * of a key
+ * @param signingKeySpec
+ * GPG key identification
+ * @return whether the {@code userId} matches
+ * @see <a href=
+ * "https://www.gnupg.org/documentation/manuals/gnupg/Specify-a-User-ID.html">GPG
+ * Documentation: How to Specify a User ID</a>
+ */
+ static boolean containsSigningKey(String userId, String signingKeySpec) {
+ if (StringUtils.isEmptyOrNull(userId)
+ || StringUtils.isEmptyOrNull(signingKeySpec)) {
+ return false;
+ }
+ String toMatch = signingKeySpec;
+ if (toMatch.startsWith("0x") && toMatch.trim().length() > 2) { //$NON-NLS-1$
+ return false; // Explicit fingerprint
+ }
+ int command = toMatch.charAt(0);
+ switch (command) {
+ case '=':
+ case '<':
+ case '@':
+ case '*':
+ toMatch = toMatch.substring(1);
+ if (toMatch.isEmpty()) {
+ return false;
+ }
+ break;
+ default:
+ break;
+ }
+ switch (command) {
+ case '=':
+ return userId.equals(toMatch);
+ case '<': {
+ int begin = userId.indexOf('<');
+ int end = userId.indexOf('>', begin + 1);
+ int stop = toMatch.indexOf('>');
+ return begin >= 0 && end > begin + 1 && stop > 0
+ && userId.substring(begin + 1, end)
+ .equals(toMatch.substring(0, stop));
+ }
+ case '@': {
+ int begin = userId.indexOf('<');
+ int end = userId.indexOf('>', begin + 1);
+ return begin >= 0 && end > begin + 1
+ && userId.substring(begin + 1, end).contains(toMatch);
+ }
+ default:
+ if (toMatch.trim().isEmpty()) {
+ return false;
+ }
+ return userId.toLowerCase(Locale.ROOT)
+ .contains(toMatch.toLowerCase(Locale.ROOT));
+ }
+ }
+
+ private String toFingerprint(String keyId) {
+ if (keyId.startsWith("0x")) { //$NON-NLS-1$
+ return keyId.substring(2);
+ }
+ return keyId;
}
private PGPPublicKey findPublicKeyByKeyId(KeyBlob keyBlob)
throws IOException {
- String keyId = signingKey.toLowerCase(Locale.ROOT);
+ String keyId = toFingerprint(signingKey).toLowerCase(Locale.ROOT);
+ if (keyId.isEmpty()) {
+ return null;
+ }
for (KeyInformation keyInfo : keyBlob.getKeyInformation()) {
String fingerprint = Hex.toHexString(keyInfo.getFingerprint())
.toLowerCase(Locale.ROOT);
if (fingerprint.endsWith(keyId)) {
- return getFirstPublicKey(keyBlob);
+ return getPublicKey(keyBlob, keyInfo.getFingerprint());
}
}
return null;
@@ -211,8 +287,8 @@ class BouncyCastleGpgKeyLocator {
private PGPPublicKey findPublicKeyByUserId(KeyBlob keyBlob)
throws IOException {
for (UserID userID : keyBlob.getUserIds()) {
- if (containsSigningKey(userID.getUserIDAsString())) {
- return getFirstPublicKey(keyBlob);
+ if (containsSigningKey(userID.getUserIDAsString(), signingKey)) {
+ return getSigningPublicKey(keyBlob);
}
}
return null;
@@ -444,7 +520,7 @@ class BouncyCastleGpgKeyLocator {
PGPUtil.getDecoderStream(new BufferedInputStream(in)),
new JcaKeyFingerprintCalculator());
- String keyId = signingkey.toLowerCase(Locale.ROOT);
+ String keyId = toFingerprint(signingkey).toLowerCase(Locale.ROOT);
Iterator<PGPSecretKeyRing> keyrings = pgpSec.getKeyRings();
while (keyrings.hasNext()) {
PGPSecretKeyRing keyRing = keyrings.next();
@@ -462,7 +538,7 @@ class BouncyCastleGpgKeyLocator {
Iterator<String> userIDs = key.getUserIDs();
while (userIDs.hasNext()) {
String userId = userIDs.next();
- if (containsSigningKey(userId)) {
+ if (containsSigningKey(userId, signingKey)) {
return key;
}
}
@@ -490,7 +566,7 @@ class BouncyCastleGpgKeyLocator {
new BufferedInputStream(in),
new JcaKeyFingerprintCalculator());
- String keyId = signingKey.toLowerCase(Locale.ROOT);
+ String keyId = toFingerprint(signingKey).toLowerCase(Locale.ROOT);
Iterator<PGPPublicKeyRing> keyrings = pgpPub.getKeyRings();
while (keyrings.hasNext()) {
PGPPublicKeyRing keyRing = keyrings.next();
@@ -507,7 +583,7 @@ class BouncyCastleGpgKeyLocator {
Iterator<String> userIDs = key.getUserIDs();
while (userIDs.hasNext()) {
String userId = userIDs.next();
- if (containsSigningKey(userId)) {
+ if (containsSigningKey(userId, signingKey)) {
return key;
}
}
@@ -517,9 +593,42 @@ class BouncyCastleGpgKeyLocator {
return null;
}
- private PGPPublicKey getFirstPublicKey(KeyBlob keyBlob) throws IOException {
- return ((PublicKeyRingBlob) keyBlob).getPGPPublicKeyRing()
- .getPublicKey();
+ private PGPPublicKey getPublicKey(KeyBlob blob, byte[] fingerprint)
+ throws IOException {
+ return ((PublicKeyRingBlob) blob).getPGPPublicKeyRing()
+ .getPublicKey(fingerprint);
+ }
+
+ private PGPPublicKey getSigningPublicKey(KeyBlob blob) throws IOException {
+ PGPPublicKey masterKey = null;
+ Iterator<PGPPublicKey> keys = ((PublicKeyRingBlob) blob)
+ .getPGPPublicKeyRing().getPublicKeys();
+ while (keys.hasNext()) {
+ PGPPublicKey key = keys.next();
+ // only consider keys that have the [S] usage flag set
+ if (isSigningKey(key)) {
+ if (key.isMasterKey()) {
+ masterKey = key;
+ } else {
+ return key;
+ }
+ }
+ }
+ // return the master key if no other signing key was found or null if
+ // the master key did not have the signing flag set
+ return masterKey;
+ }
+
+ private boolean isSigningKey(PGPPublicKey key) {
+ Iterator signatures = key.getSignatures();
+ while (signatures.hasNext()) {
+ PGPSignature sig = (PGPSignature) signatures.next();
+ if ((sig.getHashedSubPackets().getKeyFlags()
+ & PGPKeyFlags.CAN_SIGN) > 0) {
+ return true;
+ }
+ }
+ return false;
}
private KeyBox readKeyBoxFile(Path keyboxFile) throws IOException,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java
index cfe0931b47..cfa67eefdc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/internal/BouncyCastleGpgSigner.java
@@ -115,7 +115,7 @@ public class BouncyCastleGpgSigner extends GpgSigner {
NoSuchAlgorithmException, NoSuchProviderException, PGPException,
URISyntaxException {
if (gpgSigningKey == null || gpgSigningKey.isEmpty()) {
- gpgSigningKey = committer.getEmailAddress();
+ gpgSigningKey = '<' + committer.getEmailAddress() + '>';
}
BouncyCastleGpgKeyLocator keyHelper = new BouncyCastleGpgKeyLocator(
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 a77cb4ffb9..3ff38dc331 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeAlgorithm.java
@@ -83,7 +83,7 @@ public final class MergeAlgorithm {
// An special edit which acts as a sentinel value by marking the end the
// list of edits
- private final static Edit END_EDIT = new Edit(Integer.MAX_VALUE,
+ private static final Edit END_EDIT = new Edit(Integer.MAX_VALUE,
Integer.MAX_VALUE);
@SuppressWarnings("ReferenceEquality")
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeConfig.java
index cdbe3cd26c..12f353e0da 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeConfig.java
@@ -166,10 +166,10 @@ public class MergeConfig {
String mergeOptions = config.getString(
ConfigConstants.CONFIG_BRANCH_SECTION, branch,
ConfigConstants.CONFIG_KEY_MERGEOPTIONS);
- if (mergeOptions != null)
+ if (mergeOptions != null) {
return mergeOptions.split("\\s"); //$NON-NLS-1$
- else
- return new String[0];
+ }
+ return new String[0];
}
private static class MergeConfigSectionParser implements
@@ -188,10 +188,10 @@ public class MergeConfig {
@Override
public boolean equals(Object obj) {
- if (obj instanceof MergeConfigSectionParser)
+ if (obj instanceof MergeConfigSectionParser) {
return branch.equals(((MergeConfigSectionParser) obj).branch);
- else
- return false;
+ }
+ return false;
}
@Override
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 ca0e18a0ef..ca2f37abee 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
@@ -153,15 +153,16 @@ public class MergeMessageFormatter {
private static void addConflictsMessage(List<String> conflictingPaths,
StringBuilder sb) {
sb.append("Conflicts:\n"); //$NON-NLS-1$
- for (String conflictingPath : conflictingPaths)
+ for (String conflictingPath : conflictingPaths) {
sb.append('\t').append(conflictingPath).append('\n');
+ }
}
private static String joinNames(List<String> names, String singular,
String plural) {
- if (names.size() == 1)
+ if (names.size() == 1) {
return singular + " " + names.get(0); //$NON-NLS-1$
- else
- return plural + " " + StringUtils.join(names, ", ", " and "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ return plural + " " + StringUtils.join(names, ", ", " and "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
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 0b423fb5d4..34b521e85f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -143,7 +143,7 @@ public class ResolveMerger extends ThreeWayMerger {
*
* @since 3.0
*/
- protected String commitNames[];
+ protected String[] commitNames;
/**
* Index of the base tree within the {@link #tw tree walk}.
@@ -652,42 +652,40 @@ public class ResolveMerger extends ThreeWayMerger {
keep(ourDce);
// no checkout needed!
return true;
- } else {
- // same content but different mode on OURS and THEIRS.
- // Try to merge the mode and report an error if this is
- // not possible.
- int newMode = mergeFileModes(modeB, modeO, modeT);
- if (newMode != FileMode.MISSING.getBits()) {
- if (newMode == modeO)
- // ours version is preferred
- keep(ourDce);
- else {
- // the preferred version THEIRS has a different mode
- // than ours. Check it out!
- if (isWorktreeDirty(work, ourDce))
- return false;
- // we know about length and lastMod only after we have written the new content.
- // This will happen later. Set these values to 0 for know.
- DirCacheEntry e = add(tw.getRawPath(), theirs,
- DirCacheEntry.STAGE_0, EPOCH, 0);
- addToCheckout(tw.getPathString(), e, attributes);
- }
- return true;
+ }
+ // same content but different mode on OURS and THEIRS.
+ // Try to merge the mode and report an error if this is
+ // not possible.
+ int newMode = mergeFileModes(modeB, modeO, modeT);
+ if (newMode != FileMode.MISSING.getBits()) {
+ if (newMode == modeO) {
+ // ours version is preferred
+ keep(ourDce);
} else {
- // FileModes are not mergeable. We found a conflict on modes.
- // For conflicting entries we don't know lastModified and length.
- add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
- add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
- add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH,
- 0);
- unmergedPaths.add(tw.getPathString());
- mergeResults.put(
- tw.getPathString(),
- new MergeResult<>(Collections
- .<RawText> emptyList()));
+ // the preferred version THEIRS has a different mode
+ // than ours. Check it out!
+ if (isWorktreeDirty(work, ourDce)) {
+ return false;
+ }
+ // we know about length and lastMod only after we have
+ // written the new content.
+ // This will happen later. Set these values to 0 for know.
+ DirCacheEntry e = add(tw.getRawPath(), theirs,
+ DirCacheEntry.STAGE_0, EPOCH, 0);
+ addToCheckout(tw.getPathString(), e, attributes);
}
return true;
}
+ // FileModes are not mergeable. We found a conflict on modes.
+ // For conflicting entries we don't know lastModified and
+ // length.
+ add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0);
+ add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0);
+ add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0);
+ unmergedPaths.add(tw.getPathString());
+ mergeResults.put(tw.getPathString(),
+ new MergeResult<>(Collections.<RawText> emptyList()));
+ return true;
}
if (modeB == modeT && tw.idEqual(T_BASE, T_THEIRS)) {
@@ -716,21 +714,20 @@ public class ResolveMerger extends ThreeWayMerger {
addToCheckout(tw.getPathString(), e, attributes);
}
return true;
- } else {
- // we want THEIRS ... but THEIRS contains a folder or the
- // deletion of the path. Delete what's in the working tree,
- // which we know to be clean.
- if (tw.getTreeCount() > T_FILE && tw.getRawMode(T_FILE) == 0) {
- // Not present in working tree, so nothing to delete
- return true;
- }
- if (modeT != 0 && modeT == modeB) {
- // Base, ours, and theirs all contain a folder: don't delete
- return true;
- }
- addDeletion(tw.getPathString(), nonTree(modeO), attributes);
+ }
+ // we want THEIRS ... but THEIRS contains a folder or the
+ // deletion of the path. Delete what's in the working tree,
+ // which we know to be clean.
+ if (tw.getTreeCount() > T_FILE && tw.getRawMode(T_FILE) == 0) {
+ // Not present in working tree, so nothing to delete
return true;
}
+ if (modeT != 0 && modeT == modeB) {
+ // Base, ours, and theirs all contain a folder: don't delete
+ return true;
+ }
+ addDeletion(tw.getPathString(), nonTree(modeO), attributes);
+ return true;
}
if (tw.isSubtree()) {
@@ -1310,10 +1307,9 @@ public class ResolveMerger extends ThreeWayMerger {
if (getUnmergedPaths().isEmpty() && !failed()) {
resultTree = dircache.writeTree(getObjectInserter());
return true;
- } else {
- resultTree = null;
- return false;
}
+ resultTree = null;
+ return false;
}
/**
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 2fc0f4f073..d56e5c0c1e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java
@@ -143,17 +143,17 @@ public abstract class ThreeWayMerger extends Merger {
* @throws java.io.IOException
*/
protected AbstractTreeIterator mergeBase() throws IOException {
- if (baseTree != null)
+ if (baseTree != null) {
return openTree(baseTree);
+ }
RevCommit baseCommit = (baseCommitId != null) ? walk
.parseCommit(baseCommitId) : getBaseCommit(sourceCommits[0],
sourceCommits[1]);
if (baseCommit == null) {
baseCommitId = null;
return new EmptyTreeIterator();
- } else {
- baseCommitId = baseCommit.toObjectId();
- return openTree(baseCommit.getTree());
}
+ baseCommitId = baseCommit.toObjectId();
+ return openTree(baseCommit.getTree());
}
}
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 375cd32041..d9fb1b3a00 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/nls/NLS.java
@@ -133,8 +133,8 @@ public class NLS {
return b.get(type);
}
- final private Locale locale;
- final private ConcurrentHashMap<Class, TranslationBundle> map = new ConcurrentHashMap<>();
+ private final Locale locale;
+ private final ConcurrentHashMap<Class, TranslationBundle> map = new ConcurrentHashMap<>();
private NLS(Locale locale) {
this.locale = locale;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java
index 7827a9aa05..c1616b3ed8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/FanoutBucket.java
@@ -167,10 +167,10 @@ class FanoutBucket extends InMemoryNoteBucket {
@Override
public Note next() {
- if (hasNext())
+ if (hasNext()) {
return itr.next();
- else
- throw new NoSuchElementException();
+ }
+ throw new NoSuchElementException();
}
@Override
@@ -214,30 +214,31 @@ class FanoutBucket extends InMemoryNoteBucket {
NoteBucket b = table[cell];
if (b == null) {
- if (noteData == null)
+ if (noteData == null) {
return this;
+ }
LeafBucket n = new LeafBucket(prefixLen + 2);
table[cell] = n.set(noteOn, noteData, or);
cnt++;
return this;
- } else {
- NoteBucket n = b.set(noteOn, noteData, or);
- if (n == null) {
- table[cell] = null;
- cnt--;
+ }
+ NoteBucket n = b.set(noteOn, noteData, or);
+ if (n == null) {
+ table[cell] = null;
+ cnt--;
- if (cnt == 0)
- return null;
+ if (cnt == 0) {
+ return null;
+ }
- return contractIfTooSmall(noteOn, or);
+ return contractIfTooSmall(noteOn, or);
- } else if (n != b) {
- table[cell] = n;
- }
- return this;
+ } else if (n != b) {
+ table[cell] = n;
}
+ return this;
}
InMemoryNoteBucket contractIfTooSmall(AnyObjectId noteOn, ObjectReader or)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java
index 6723b6309c..0fa2a6306c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/LeafBucket.java
@@ -129,10 +129,10 @@ class LeafBucket extends InMemoryNoteBucket {
@Override
public Note next() {
- if (hasNext())
+ if (hasNext()) {
return notes[idx++];
- else
- throw new NoSuchElementException();
+ }
+ throw new NoSuchElementException();
}
@Override
@@ -156,25 +156,23 @@ class LeafBucket extends InMemoryNoteBucket {
notes[p].setData(noteData.copy());
return this;
- } else {
- System.arraycopy(notes, p + 1, notes, p, cnt - p - 1);
- cnt--;
- return 0 < cnt ? this : null;
}
+ System.arraycopy(notes, p + 1, notes, p, cnt - p - 1);
+ cnt--;
+ return 0 < cnt ? this : null;
} else if (noteData != null) {
if (shouldSplit()) {
return split().set(noteOn, noteData, or);
-
- } else {
- growIfFull();
- p = -(p + 1);
- if (p < cnt)
- System.arraycopy(notes, p, notes, p + 1, cnt - p);
- notes[p] = new Note(noteOn, noteData.copy());
- cnt++;
- return this;
}
+ growIfFull();
+ p = -(p + 1);
+ if (p < cnt) {
+ System.arraycopy(notes, p, notes, p + 1, cnt - p);
+ }
+ notes[p] = new Note(noteOn, noteData.copy());
+ cnt++;
+ return this;
} else {
return this;
@@ -234,12 +232,10 @@ class LeafBucket extends InMemoryNoteBucket {
InMemoryNoteBucket append(Note note) {
if (shouldSplit()) {
return split().append(note);
-
- } else {
- growIfFull();
- notes[cnt++] = note;
- return this;
}
+ growIfFull();
+ notes[cnt++] = note;
+ return this;
}
private void growIfFull() {
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 cbef61338f..e4eef433d8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMap.java
@@ -278,10 +278,10 @@ public class NoteMap implements Iterable<Note> {
public byte[] getCachedBytes(AnyObjectId id, int sizeLimit)
throws LargeObjectException, MissingObjectException, IOException {
ObjectId dataId = get(id);
- if (dataId != null)
+ if (dataId != null) {
return reader.open(dataId).getCachedBytes(sizeLimit);
- else
- return null;
+ }
+ return null;
}
/**
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 ba7223b8f0..6ff1402900 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java
@@ -307,10 +307,10 @@ public class NoteMapMerger {
private static InMemoryNoteBucket addIfNotNull(InMemoryNoteBucket result,
Note note) {
- if (note != null)
+ if (note != null) {
return result.append(note);
- else
- return result;
+ }
+ return result;
}
private NonNoteEntry mergeNonNotes(NonNoteEntry baseList,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteParser.java
index 8ef3af10ad..7dfc47deb0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteParser.java
@@ -181,9 +181,8 @@ final class NoteParser extends CanonicalTreeParser {
} catch (ArrayIndexOutOfBoundsException notHex) {
return -1;
}
- } else {
- return -1;
}
+ return -1;
}
private void storeNonNote() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/patch/BinaryHunk.java b/org.eclipse.jgit/src/org/eclipse/jgit/patch/BinaryHunk.java
index 95391ec565..c1ee701cf1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/BinaryHunk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/BinaryHunk.java
@@ -58,7 +58,7 @@ public class BinaryHunk {
private static final byte[] DELTA = encodeASCII("delta "); //$NON-NLS-1$
/** Type of information stored in a binary hunk. */
- public static enum Type {
+ public enum Type {
/** The full content is stored, deflated. */
LITERAL_DEFLATED,
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 74eec81209..244f80483a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/CombinedHunkHeader.java
@@ -58,7 +58,7 @@ import org.eclipse.jgit.util.MutableInteger;
* Hunk header for a hunk appearing in a "diff --cc" style patch.
*/
public class CombinedHunkHeader extends HunkHeader {
- private static abstract class CombinedOldImage extends OldImage {
+ private abstract static class CombinedOldImage extends OldImage {
int nContext;
}
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 1f4beb017f..959109c2e7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FileHeader.java
@@ -104,7 +104,7 @@ public class FileHeader extends DiffEntry {
static final byte[] NEW_NAME = encodeASCII("+++ "); //$NON-NLS-1$
/** Type of patch used by this file. */
- public static enum PatchType {
+ public enum PatchType {
/** A traditional unified diff style patch of a text file. */
UNIFIED,
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 10ea778343..389668733a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/patch/FormatError.java
@@ -54,7 +54,7 @@ import org.eclipse.jgit.util.RawParseUtils;
*/
public class FormatError {
/** Classification of an error. */
- public static enum Severity {
+ public enum Severity {
/** The error is unexpected, but can be worked around. */
WARNING,
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 45508ce027..2bb45c55dc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotCommitList.java
@@ -391,11 +391,10 @@ public class PlotCommitList<L extends PlotLane> extends
return pos.intValue();
}
return positionsAllocated++;
- } else {
- final Integer min = freePositions.first();
- freePositions.remove(min);
- return min.intValue();
}
+ final Integer min = freePositions.first();
+ freePositions.remove(min);
+ return min.intValue();
}
/**
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 ee18fe7c2f..19e40b562b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/PlotWalk.java
@@ -176,11 +176,10 @@ public class PlotWalk extends RevWalk {
Collection<Ref> list = reverseRefMap.get(commitId);
if (list == null) {
return PlotCommit.NO_REFS;
- } else {
- Ref[] tags = list.toArray(new Ref[0]);
- Arrays.sort(tags, new PlotRefComparator());
- return tags;
}
+ Ref[] tags = list.toArray(new Ref[0]);
+ Arrays.sort(tags, new PlotRefComparator());
+ return tags;
}
class PlotRefComparator implements Comparator<Ref> {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmapCalculator.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmapCalculator.java
deleted file mode 100644
index 14e95670aa..0000000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmapCalculator.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2019, Google LLC.
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.eclipse.jgit.revwalk;
-
-import static java.util.Objects.requireNonNull;
-
-import java.io.IOException;
-
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.internal.revwalk.AddToBitmapFilter;
-import org.eclipse.jgit.lib.BitmapIndex;
-import org.eclipse.jgit.lib.BitmapIndex.Bitmap;
-import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
-import org.eclipse.jgit.lib.ProgressMonitor;
-
-/**
- * Calculate the bitmap indicating what other commits are reachable from certain
- * commit.
- * <p>
- * This bitmap refers only to commits. For a bitmap with ALL objects reachable
- * from certain object, see {@code BitmapWalker}.
- */
-class BitmapCalculator {
-
- private final RevWalk walk;
- private final BitmapIndex bitmapIndex;
-
- BitmapCalculator(RevWalk walk) throws IOException {
- this.walk = walk;
- this.bitmapIndex = requireNonNull(
- walk.getObjectReader().getBitmapIndex());
- }
-
- /**
- * Get the reachability bitmap from certain commit to other commits.
- * <p>
- * This will return a precalculated bitmap if available or walk building one
- * until finding a precalculated bitmap (and returning the union).
- * <p>
- * Beware that the returned bitmap it is guaranteed to include ONLY the
- * commits reachable from the initial commit. It COULD include other objects
- * (because precalculated bitmaps have them) but caller shouldn't count on
- * that. See {@link BitmapWalker} for a full reachability bitmap.
- *
- * @param start
- * the commit. Use {@code walk.parseCommit(objectId)} to get this
- * object from the id.
- * @param pm
- * progress monitor. Updated by one per commit browsed in the
- * graph
- * @return the bitmap of reachable commits (and maybe some extra objects)
- * for the commit
- * @throws MissingObjectException
- * the supplied id doesn't exist
- * @throws IncorrectObjectTypeException
- * the supplied id doesn't refer to a commit or a tag
- * @throws IOException
- * if the walk cannot open a packfile or loose object
- */
- BitmapBuilder getBitmap(RevCommit start, ProgressMonitor pm)
- throws MissingObjectException,
- IncorrectObjectTypeException, IOException {
- Bitmap precalculatedBitmap = bitmapIndex.getBitmap(start);
- if (precalculatedBitmap != null) {
- return asBitmapBuilder(precalculatedBitmap);
- }
-
- walk.reset();
- walk.sort(RevSort.TOPO);
- walk.markStart(start);
- // Unbounded walk. If the repo has bitmaps, it should bump into one at
- // some point.
-
- BitmapBuilder bitmapResult = bitmapIndex.newBitmapBuilder();
- walk.setRevFilter(new AddToBitmapFilter(bitmapResult));
- while (walk.next() != null) {
- // Iterate through all of the commits. The BitmapRevFilter does
- // the work.
- //
- // filter.include returns true for commits that do not have
- // a bitmap in bitmapIndex and are not reachable from a
- // bitmap in bitmapIndex encountered earlier in the walk.
- // Thus the number of commits returned by next() measures how
- // much history was traversed without being able to make use
- // of bitmaps.
- pm.update(1);
- }
-
- return bitmapResult;
- }
-
- private BitmapBuilder asBitmapBuilder(Bitmap bitmap) {
- return bitmapIndex.newBitmapBuilder().or(bitmap);
- }
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmappedReachabilityChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmappedReachabilityChecker.java
index 6e510f677b..bf831e3313 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmappedReachabilityChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/BitmappedReachabilityChecker.java
@@ -45,13 +45,18 @@ package org.eclipse.jgit.revwalk;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Iterator;
import java.util.List;
import java.util.Optional;
+import java.util.stream.Stream;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.BitmapIndex;
+import org.eclipse.jgit.lib.BitmapIndex.Bitmap;
import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
-import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.revwalk.filter.RevFilter;
/**
* Checks the reachability using bitmaps.
@@ -84,37 +89,108 @@ class BitmappedReachabilityChecker implements ReachabilityChecker {
* Check all targets are reachable from the starters.
* <p>
* In this implementation, it is recommended to put the most popular
- * starters (e.g. refs/heads tips) at the beginning of the collection
+ * starters (e.g. refs/heads tips) at the beginning.
*/
@Override
public Optional<RevCommit> areAllReachable(Collection<RevCommit> targets,
- Collection<RevCommit> starters) throws MissingObjectException,
+ Stream<RevCommit> starters) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
- BitmapCalculator calculator = new BitmapCalculator(walk);
- /**
- * Iterate over starters bitmaps and remove targets as they become
- * reachable.
- *
- * Building the total starters bitmap has the same cost (iterating over
- * all starters adding the bitmaps) and this gives us the chance to
- * shorcut the loop.
- *
- * This is based on the assuption that most of the starters will have
- * the reachability bitmap precalculated. If many require a walk, the
- * walk.reset() could start to take too much time.
- */
List<RevCommit> remainingTargets = new ArrayList<>(targets);
- for (RevCommit starter : starters) {
- BitmapBuilder starterBitmap = calculator.getBitmap(starter,
- NullProgressMonitor.INSTANCE);
- remainingTargets.removeIf(starterBitmap::contains);
- if (remainingTargets.isEmpty()) {
- return Optional.empty();
+
+ walk.reset();
+ walk.sort(RevSort.TOPO);
+
+ // Filter emits only commits that are unreachable from previously
+ // visited commits. Internally it keeps a bitmap of everything
+ // reachable so far, which we use to discard reachable targets.
+ BitmapIndex repoBitmaps = walk.getObjectReader().getBitmapIndex();
+ ReachedFilter reachedFilter = new ReachedFilter(repoBitmaps);
+ walk.setRevFilter(reachedFilter);
+
+ Iterator<RevCommit> startersIter = starters.iterator();
+ while (startersIter.hasNext()) {
+ walk.markStart(startersIter.next());
+ while (walk.next() != null) {
+ remainingTargets.removeIf(reachedFilter::isReachable);
+
+ if (remainingTargets.isEmpty()) {
+ return Optional.empty();
+ }
}
+ walk.reset();
}
return Optional.of(remainingTargets.get(0));
}
+ /**
+ * This filter emits commits that were not bitmap-reachable from anything
+ * visited before. Or in other words, commits that add something (themselves
+ * or their bitmap) to the "reached" bitmap.
+ *
+ * Current progress can be queried via {@link #isReachable(RevCommit)}.
+ */
+ private static class ReachedFilter extends RevFilter {
+
+ private final BitmapIndex repoBitmaps;
+ private final BitmapBuilder reached;
+
+ /**
+ * Create a filter that emits only previously unreachable commits.
+ *
+ * @param repoBitmaps
+ * bitmap index of the repo
+ */
+ public ReachedFilter(BitmapIndex repoBitmaps) {
+ this.repoBitmaps = repoBitmaps;
+ this.reached = repoBitmaps.newBitmapBuilder();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final boolean include(RevWalk walker, RevCommit cmit) {
+ Bitmap commitBitmap;
+
+ if (reached.contains(cmit)) {
+ // already seen or included
+ dontFollow(cmit);
+ return false;
+ }
+
+ if ((commitBitmap = repoBitmaps.getBitmap(cmit)) != null) {
+ reached.or(commitBitmap);
+ // Emit the commit because there are new contents in the bitmap
+ // but don't follow parents (they are already in the bitmap)
+ dontFollow(cmit);
+ return true;
+ }
+
+ // No bitmaps, keep going
+ reached.addObject(cmit, Constants.OBJ_COMMIT);
+ return true;
+ }
+
+ private static final void dontFollow(RevCommit cmit) {
+ for (RevCommit p : cmit.getParents()) {
+ p.add(RevFlag.SEEN);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final RevFilter clone() {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final boolean requiresCommitBody() {
+ return false;
+ }
+
+ boolean isReachable(RevCommit commit) {
+ return reached.contains(commit);
+ }
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PedestrianReachabilityChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PedestrianReachabilityChecker.java
index bba3c5cff3..da9e75992f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PedestrianReachabilityChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/PedestrianReachabilityChecker.java
@@ -44,7 +44,9 @@ package org.eclipse.jgit.revwalk;
import java.io.IOException;
import java.util.Collection;
+import java.util.Iterator;
import java.util.Optional;
+import java.util.stream.Stream;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -75,7 +77,7 @@ class PedestrianReachabilityChecker implements ReachabilityChecker {
@Override
public Optional<RevCommit> areAllReachable(Collection<RevCommit> targets,
- Collection<RevCommit> starters)
+ Stream<RevCommit> starters)
throws MissingObjectException, IncorrectObjectTypeException,
IOException {
walk.reset();
@@ -87,8 +89,9 @@ class PedestrianReachabilityChecker implements ReachabilityChecker {
walk.markStart(target);
}
- for (RevCommit starter : starters) {
- walk.markUninteresting(starter);
+ Iterator<RevCommit> iterator = starters.iterator();
+ while (iterator.hasNext()) {
+ walk.markUninteresting(iterator.next());
}
return Optional.ofNullable(walk.next());
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 2ed06d1769..6a9c641318 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ReachabilityChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ReachabilityChecker.java
@@ -45,6 +45,7 @@ package org.eclipse.jgit.revwalk;
import java.io.IOException;
import java.util.Collection;
import java.util.Optional;
+import java.util.stream.Stream;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -82,9 +83,43 @@ public interface ReachabilityChecker {
* @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.
+ * <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.
+ * @since 5.6
*/
Optional<RevCommit> areAllReachable(Collection<RevCommit> targets,
- Collection<RevCommit> starters)
+ Stream<RevCommit> starters)
throws MissingObjectException, IncorrectObjectTypeException,
IOException;
}
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 2e26641ebc..b77407bb38 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RewriteGenerator.java
@@ -103,11 +103,15 @@ class RewriteGenerator extends Generator {
final int nParents = pList.length;
for (int i = 0; i < nParents; i++) {
final RevCommit oldp = pList[i];
- if (firstParent && i > 0) {
- c.parents = new RevCommit[] { rewrite(oldp) };
+ final RevCommit newp = rewrite(oldp);
+ if (firstParent) {
+ if (newp == null) {
+ c.parents = RevCommit.NO_PARENTS;
+ } else {
+ c.parents = new RevCommit[] { newp };
+ }
return c;
}
- final RevCommit newp = rewrite(oldp);
if (oldp != newp) {
pList[i] = newp;
rewrote = true;
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 a2c9ef6da4..e0325c29f9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TopoSortGenerator.java
@@ -80,11 +80,11 @@ class TopoSortGenerator extends Generator {
if (c == null) {
break;
}
- for (int i = 0; i < c.parents.length; i++) {
- if (firstParent && i > 0) {
+ for (RevCommit p : c.parents) {
+ p.inDegree++;
+ if (firstParent) {
break;
}
- c.parents[i].inDegree++;
}
pending.add(c);
}
@@ -119,11 +119,7 @@ class TopoSortGenerator extends Generator {
// All of our children have already produced,
// so it is OK for us to produce now as well.
//
- for (int i = 0; i < c.parents.length; i++) {
- if (firstParent && i > 0) {
- break;
- }
- RevCommit p = c.parents[i];
+ for (RevCommit p : c.parents) {
if (--p.inDegree == 0 && (p.flags & TOPO_DELAY) != 0) {
// This parent tried to come before us, but we are
// his last child. unpop the parent so it goes right
@@ -132,6 +128,9 @@ class TopoSortGenerator extends Generator {
p.flags &= ~TOPO_DELAY;
pending.unpop(p);
}
+ if (firstParent) {
+ break;
+ }
}
return c;
}
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 f7c3218850..090d1e110c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/TreeRevFilter.java
@@ -169,19 +169,19 @@ public class TreeRevFilter extends RevFilter {
//
c.flags |= rewriteFlag;
return false;
- } else {
- // We have interesting items, but neither of the special
- // cases denoted above.
+ }
+
+ // We have interesting items, but neither of the special
+ // cases denoted above.
+ //
+ if (adds > 0 && tw.getFilter() instanceof FollowFilter) {
+ // One of the paths we care about was added in this
+ // commit. We need to update our filter to its older
+ // name, if we can discover it. Find out what that is.
//
- if (adds > 0 && tw.getFilter() instanceof FollowFilter) {
- // One of the paths we care about was added in this
- // 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);
- }
- return true;
+ updateFollowFilter(trees, ((FollowFilter) tw.getFilter()).cfg);
}
+ return true;
} else if (nParents == 0) {
// We have no parents to compare against. Consider us to be
// REWRITE only if we have no paths matching our filter.
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 5bb8153a58..f1ff9e6ba0 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
@@ -77,7 +77,7 @@ import org.slf4j.LoggerFactory;
* The configuration file that is stored in the file of the file system.
*/
public class FileBasedConfig extends StoredConfig {
- private final static Logger LOG = LoggerFactory
+ private static final Logger LOG = LoggerFactory
.getLogger(FileBasedConfig.class);
private final File configFile;
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 e6e3d4fb12..645da0a068 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
@@ -271,6 +271,20 @@ public class PackStatistics {
public long treesTraversed;
/**
+ * Amount of packfile uris sent to the client to download via HTTP.
+ *
+ * @since 5.6
+ */
+ public long offloadedPackfiles;
+
+ /**
+ * Total size (in bytes) offloaded to HTTP downloads.
+ *
+ * @since 5.6
+ */
+ public long offloadedPackfileSize;
+
+ /**
* Statistics about each object type in the pack (commits, tags, trees
* and blobs.)
*/
@@ -598,6 +612,22 @@ public class PackStatistics {
}
/**
+ * @return amount of packfiles offloaded (sent as "packfile-uri")/
+ * @since 5.6
+ */
+ public long getOffloadedPackfiles() {
+ return statistics.offloadedPackfiles;
+ }
+
+ /**
+ * @return total size (in bytes) offloaded to HTTP downloads.
+ * @since 5.6
+ */
+ public long getOffloadedPackfilesSize() {
+ return statistics.offloadedPackfileSize;
+ }
+
+ /**
* Get total time spent processing this pack.
*
* @return total time spent processing this pack.
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 e5559dea09..2e5776d646 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
@@ -57,6 +57,7 @@ import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.BaseRepositoryBuilder;
import org.eclipse.jgit.lib.BlobBasedConfig;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ConfigConstants;
@@ -66,6 +67,7 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryBuilder;
+import org.eclipse.jgit.lib.RepositoryBuilderFactory;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
@@ -260,15 +262,41 @@ public class SubmoduleWalk implements AutoCloseable {
*/
public static Repository getSubmoduleRepository(final File parent,
final String path, FS fs) throws IOException {
+ return getSubmoduleRepository(parent, path, fs,
+ new RepositoryBuilder());
+ }
+
+ /**
+ * Get submodule repository at path, using the specified file system
+ * abstraction and the specified builder
+ *
+ * @param parent
+ * {@link Repository} that contains the submodule
+ * @param path
+ * of the working tree of the submodule
+ * @param fs
+ * {@link FS} to use
+ * @param builder
+ * {@link BaseRepositoryBuilder} to use to build the submodule
+ * repository
+ * @return the {@link Repository} of the submodule, or {@code null} if it
+ * doesn't exist
+ * @throws IOException
+ * on errors
+ * @since 5.6
+ */
+ public static Repository getSubmoduleRepository(File parent, String path,
+ FS fs, BaseRepositoryBuilder<?, ? extends Repository> builder)
+ throws IOException {
File subWorkTree = new File(parent, path);
- if (!subWorkTree.isDirectory())
+ if (!subWorkTree.isDirectory()) {
return null;
- File workTree = new File(parent, path);
+ }
try {
- return new RepositoryBuilder() //
+ return builder //
.setMustExist(true) //
.setFS(fs) //
- .setWorkTree(workTree) //
+ .setWorkTree(subWorkTree) //
.build();
} catch (RepositoryNotFoundException e) {
return null;
@@ -366,6 +394,8 @@ public class SubmoduleWalk implements AutoCloseable {
private Map<String, String> pathToName;
+ private RepositoryBuilderFactory factory;
+
/**
* Create submodule generator
*
@@ -639,7 +669,25 @@ public class SubmoduleWalk implements AutoCloseable {
}
/**
- * The module name for the current submodule entry (used for the section name of .git/config)
+ * Sets the {@link RepositoryBuilderFactory} to use for creating submodule
+ * repositories. If none is set, a plain {@link RepositoryBuilder} is used.
+ *
+ * @param factory
+ * to set
+ * @since 5.6
+ */
+ public void setBuilderFactory(RepositoryBuilderFactory factory) {
+ this.factory = factory;
+ }
+
+ private BaseRepositoryBuilder<?, ? extends Repository> getBuilder() {
+ return factory != null ? factory.get() : new RepositoryBuilder();
+ }
+
+ /**
+ * The module name for the current submodule entry (used for the section
+ * name of .git/config)
+ *
* @since 4.10
* @return name
*/
@@ -735,6 +783,13 @@ public class SubmoduleWalk implements AutoCloseable {
*/
public IgnoreSubmoduleMode getModulesIgnore() throws IOException,
ConfigInvalidException {
+ IgnoreSubmoduleMode mode = repoConfig.getEnum(
+ IgnoreSubmoduleMode.values(),
+ ConfigConstants.CONFIG_SUBMODULE_SECTION, getModuleName(),
+ ConfigConstants.CONFIG_KEY_IGNORE, null);
+ if (mode != null) {
+ return mode;
+ }
lazyLoadModulesConfig();
return modulesConfig.getEnum(IgnoreSubmoduleMode.values(),
ConfigConstants.CONFIG_SUBMODULE_SECTION, getModuleName(),
@@ -748,7 +803,8 @@ public class SubmoduleWalk implements AutoCloseable {
* @throws java.io.IOException
*/
public Repository getRepository() throws IOException {
- return getSubmoduleRepository(repository, path);
+ return getSubmoduleRepository(repository.getWorkTree(), path,
+ repository.getFS(), getBuilder());
}
/**
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 4bf0d2692a..ed900121be 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AbstractAdvertiseRefsHook.java
@@ -67,7 +67,7 @@ public abstract class AbstractAdvertiseRefsHook implements AdvertiseRefsHook {
/** {@inheritDoc} */
@Override
- public void advertiseRefs(BaseReceivePack receivePack)
+ public void advertiseRefs(ReceivePack receivePack)
throws ServiceMayNotContinueException {
Map<String, Ref> refs = getAdvertisedRefs(receivePack.getRepository(),
receivePack.getRevWalk());
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 8512f2d9fe..eb1aef9ad7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHook.java
@@ -51,8 +51,8 @@ public interface AdvertiseRefsHook {
/**
* A simple hook that advertises the default refs.
* <p>
- * The method implementations do nothing to preserve the default behavior; see
- * {@link UploadPack#setAdvertisedRefs(java.util.Map)} and
+ * The method implementations do nothing to preserve the default behavior;
+ * see {@link UploadPack#setAdvertisedRefs(java.util.Map)} and
* {@link ReceivePack#setAdvertisedRefs(java.util.Map,java.util.Set)}.
*/
AdvertiseRefsHook DEFAULT = new AdvertiseRefsHook() {
@@ -62,7 +62,7 @@ public interface AdvertiseRefsHook {
}
@Override
- public void advertiseRefs(BaseReceivePack receivePack) {
+ public void advertiseRefs(ReceivePack receivePack) {
// Do nothing.
}
};
@@ -89,7 +89,8 @@ public interface AdvertiseRefsHook {
* if necessary.
* @throws org.eclipse.jgit.transport.ServiceMayNotContinueException
* abort; the message will be sent to the user.
+ * @since 5.6
*/
- void advertiseRefs(BaseReceivePack receivePack)
+ void advertiseRefs(ReceivePack receivePack)
throws ServiceMayNotContinueException;
}
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 12238a1f77..54c19783e3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/AdvertiseRefsHookChain.java
@@ -53,7 +53,7 @@ import java.util.List;
* modify the results of the previous hooks in the chain by calling
* {@link org.eclipse.jgit.transport.UploadPack#getAdvertisedRefs()}, or
* {@link org.eclipse.jgit.transport.ReceivePack#getAdvertisedRefs()} or
- * {@link org.eclipse.jgit.transport.BaseReceivePack#getAdvertisedObjects()}.
+ * {@link org.eclipse.jgit.transport.ReceivePack#getAdvertisedObjects()}.
*/
public class AdvertiseRefsHookChain implements AdvertiseRefsHook {
private final AdvertiseRefsHook[] hooks;
@@ -82,7 +82,7 @@ public class AdvertiseRefsHookChain implements AdvertiseRefsHook {
/** {@inheritDoc} */
@Override
- public void advertiseRefs(BaseReceivePack rp)
+ public void advertiseRefs(ReceivePack rp)
throws ServiceMayNotContinueException {
for (int i = 0; i < count; i++)
hooks[i].advertiseRefs(rp);
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 35ea35ecb8..12ade4d195 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
@@ -391,7 +391,7 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
refNameEnd = refLine.length();
} else if (refLine.startsWith("ng ")) { //$NON-NLS-1$
ok = false;
- refNameEnd = refLine.indexOf(" ", 3); //$NON-NLS-1$
+ refNameEnd = refLine.indexOf(' ', 3);
}
if (refNameEnd == -1)
throw new PackProtocolException(MessageFormat.format(JGitText.get().unexpectedReportLine2
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
deleted file mode 100644
index 36a10cc6b9..0000000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
+++ /dev/null
@@ -1,1971 +0,0 @@
-/*
- * Copyright (C) 2008-2010, Google Inc.
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.transport;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC;
-import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_DELETE_REFS;
-import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_OFS_DELTA;
-import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_OPTIONS;
-import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_QUIET;
-import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS;
-import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SIDE_BAND_64K;
-import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
-import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA;
-import static org.eclipse.jgit.transport.SideBandOutputStream.CH_ERROR;
-import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS;
-import static org.eclipse.jgit.transport.SideBandOutputStream.MAX_BUF;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jgit.annotations.Nullable;
-import org.eclipse.jgit.errors.InvalidObjectIdException;
-import org.eclipse.jgit.errors.LargeObjectException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.errors.PackProtocolException;
-import org.eclipse.jgit.errors.TooLargePackException;
-import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.internal.storage.file.PackLock;
-import org.eclipse.jgit.internal.submodule.SubmoduleValidator;
-import org.eclipse.jgit.internal.submodule.SubmoduleValidator.SubmoduleValidationException;
-import org.eclipse.jgit.internal.transport.parser.FirstCommand;
-import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.GitmoduleEntry;
-import org.eclipse.jgit.lib.NullProgressMonitor;
-import org.eclipse.jgit.lib.ObjectChecker;
-import org.eclipse.jgit.lib.ObjectDatabase;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectIdSubclassMap;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.ProgressMonitor;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.ObjectWalk;
-import org.eclipse.jgit.revwalk.RevBlob;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevFlag;
-import org.eclipse.jgit.revwalk.RevObject;
-import org.eclipse.jgit.revwalk.RevSort;
-import org.eclipse.jgit.revwalk.RevTree;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.PacketLineIn.InputOverLimitIOException;
-import org.eclipse.jgit.transport.ReceiveCommand.Result;
-import org.eclipse.jgit.util.io.InterruptTimer;
-import org.eclipse.jgit.util.io.LimitedInputStream;
-import org.eclipse.jgit.util.io.TimeoutInputStream;
-import org.eclipse.jgit.util.io.TimeoutOutputStream;
-
-/**
- * Base implementation of the side of a push connection that receives objects.
- * <p>
- * Contains high-level operations for initializing and closing streams,
- * advertising refs, reading commands, and receiving and applying a pack.
- * Subclasses compose these operations into full service implementations.
- */
-public abstract class BaseReceivePack {
- /**
- * Data in the first line of a request, the line itself plus capabilities.
- *
- * @deprecated Use {@link FirstCommand} instead.
- */
- @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() {
- return command.getCapabilities();
- }
- }
-
- /** Database we write the stored objects into. */
- final Repository db;
-
- /** Revision traversal support over {@link #db}. */
- final RevWalk walk;
-
- /**
- * Is the client connection a bi-directional socket or pipe?
- * <p>
- * If true, this class assumes it can perform multiple read and write cycles
- * with the client over the input and output streams. This matches the
- * functionality available with a standard TCP/IP connection, or a local
- * operating system or in-memory pipe.
- * <p>
- * If false, this class runs in a read everything then output results mode,
- * making it suitable for single round-trip systems RPCs such as HTTP.
- */
- private boolean biDirectionalPipe = true;
-
- /** Expecting data after the pack footer */
- private boolean expectDataAfterPackFooter;
-
- /** Should an incoming transfer validate objects? */
- private ObjectChecker objectChecker;
-
- /** Should an incoming transfer permit create requests? */
- private boolean allowCreates;
-
- /** Should an incoming transfer permit delete requests? */
- private boolean allowAnyDeletes;
- private boolean allowBranchDeletes;
-
- /** Should an incoming transfer permit non-fast-forward requests? */
- private boolean allowNonFastForwards;
-
- /** Should an incoming transfer permit push options? **/
- private boolean allowPushOptions;
-
- /**
- * Should the requested ref updates be performed as a single atomic
- * transaction?
- */
- private boolean atomic;
-
- private boolean allowOfsDelta;
- private boolean allowQuiet = true;
-
- /** Identity to record action as within the reflog. */
- private PersonIdent refLogIdent;
-
- /** Hook used while advertising the refs to the client. */
- private AdvertiseRefsHook advertiseRefsHook;
-
- /** Filter used while advertising the refs to the client. */
- RefFilter refFilter;
-
- /** Timeout in seconds to wait for client interaction. */
- private int timeout;
-
- /** Timer to manage {@link #timeout}. */
- private InterruptTimer timer;
-
- private TimeoutInputStream timeoutIn;
-
- // Original stream passed to init(), since rawOut may be wrapped in a
- // sideband.
- private OutputStream origOut;
-
- /** Raw input stream. */
- protected InputStream rawIn;
-
- /** Raw output stream. */
- protected OutputStream rawOut;
-
- /** Optional message output stream. */
- protected OutputStream msgOut;
- private SideBandOutputStream errOut;
-
- /** Packet line input stream around {@link #rawIn}. */
- protected PacketLineIn pckIn;
-
- /** Packet line output stream around {@link #rawOut}. */
- protected PacketLineOut pckOut;
-
- private final MessageOutputWrapper msgOutWrapper = new MessageOutputWrapper();
-
- private PackParser parser;
-
- /** The refs we advertised as existing at the start of the connection. */
- Map<String, Ref> refs;
-
- /** All SHA-1s shown to the client, which can be possible edges. */
- Set<ObjectId> advertisedHaves;
-
- /** Capabilities requested by the client. */
- private Set<String> enabledCapabilities;
- String userAgent;
- private Set<ObjectId> clientShallowCommits;
- private List<ReceiveCommand> commands;
- private long maxCommandBytes;
- private long maxDiscardBytes;
-
- private StringBuilder advertiseError;
-
- /** If {@link BasePackPushConnection#CAPABILITY_SIDE_BAND_64K} is enabled. */
- private boolean sideBand;
-
- private boolean quiet;
-
- /** Lock around the received pack file, while updating refs. */
- private PackLock packLock;
-
- private boolean checkReferencedIsReachable;
-
- /** Git object size limit */
- private long maxObjectSizeLimit;
-
- /** Total pack size limit */
- private long maxPackSizeLimit = -1;
-
- /** The size of the received pack, including index size */
- private Long packSize;
-
- private PushCertificateParser pushCertificateParser;
- private SignedPushConfig signedPushConfig;
- PushCertificate pushCert;
- private ReceivedPackStatistics stats;
-
- /**
- * Get the push certificate used to verify the pusher's identity.
- * <p>
- * Only valid after commands are read from the wire.
- *
- * @return the parsed certificate, or null if push certificates are disabled
- * or no cert was presented by the client.
- * @since 4.1
- * @deprecated use {@link ReceivePack#getPushCertificate}.
- */
- @Deprecated
- public abstract PushCertificate getPushCertificate();
-
- /**
- * Set the push certificate used to verify the pusher's identity.
- * <p>
- * Should only be called if reconstructing an instance without going through
- * the normal {@link #recvCommands()} flow.
- *
- * @param cert
- * the push certificate to set.
- * @since 4.1
- * @deprecated use {@link ReceivePack#setPushCertificate(PushCertificate)}.
- */
- @Deprecated
- public abstract void setPushCertificate(PushCertificate cert);
-
- /**
- * Create a new pack receive for an open repository.
- *
- * @param into
- * the destination repository.
- */
- protected BaseReceivePack(Repository into) {
- db = into;
- walk = new RevWalk(db);
- walk.setRetainBody(false);
-
- TransferConfig tc = db.getConfig().get(TransferConfig.KEY);
- objectChecker = tc.newReceiveObjectChecker();
-
- ReceiveConfig rc = db.getConfig().get(ReceiveConfig::new);
- allowCreates = rc.allowCreates;
- allowAnyDeletes = true;
- allowBranchDeletes = rc.allowDeletes;
- allowNonFastForwards = rc.allowNonFastForwards;
- allowOfsDelta = rc.allowOfsDelta;
- allowPushOptions = rc.allowPushOptions;
- maxCommandBytes = rc.maxCommandBytes;
- maxDiscardBytes = rc.maxDiscardBytes;
- advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
- refFilter = RefFilter.DEFAULT;
- advertisedHaves = new HashSet<>();
- clientShallowCommits = new HashSet<>();
- signedPushConfig = rc.signedPush;
- }
-
- /** Configuration for receive operations. */
- protected static class ReceiveConfig {
- final boolean allowCreates;
- final boolean allowDeletes;
- final boolean allowNonFastForwards;
- final boolean allowOfsDelta;
- final boolean allowPushOptions;
- final long maxCommandBytes;
- final long maxDiscardBytes;
- final SignedPushConfig signedPush;
-
- ReceiveConfig(Config config) {
- allowCreates = true;
- allowDeletes = !config.getBoolean("receive", "denydeletes", false); //$NON-NLS-1$ //$NON-NLS-2$
- allowNonFastForwards = !config.getBoolean("receive", //$NON-NLS-1$
- "denynonfastforwards", false); //$NON-NLS-1$
- allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", //$NON-NLS-1$ //$NON-NLS-2$
- true);
- allowPushOptions = config.getBoolean("receive", "pushoptions", //$NON-NLS-1$ //$NON-NLS-2$
- false);
- maxCommandBytes = config.getLong("receive", //$NON-NLS-1$
- "maxCommandBytes", //$NON-NLS-1$
- 3 << 20);
- maxDiscardBytes = config.getLong("receive", //$NON-NLS-1$
- "maxCommandDiscardBytes", //$NON-NLS-1$
- -1);
- signedPush = SignedPushConfig.KEY.parse(config);
- }
- }
-
- /**
- * Output stream that wraps the current {@link #msgOut}.
- * <p>
- * We don't want to expose {@link #msgOut} directly because it can change
- * several times over the course of a session.
- */
- class MessageOutputWrapper extends OutputStream {
- @Override
- public void write(int ch) {
- if (msgOut != null) {
- try {
- msgOut.write(ch);
- } catch (IOException e) {
- // Ignore write failures.
- }
- }
- }
-
- @Override
- public void write(byte[] b, int off, int len) {
- if (msgOut != null) {
- try {
- msgOut.write(b, off, len);
- } catch (IOException e) {
- // Ignore write failures.
- }
- }
- }
-
- @Override
- public void write(byte[] b) {
- write(b, 0, b.length);
- }
-
- @Override
- public void flush() {
- if (msgOut != null) {
- try {
- msgOut.flush();
- } catch (IOException e) {
- // Ignore write failures.
- }
- }
- }
- }
-
- /**
- * Get the process name used for pack lock messages.
- *
- * @return the process name used for pack lock messages.
- */
- protected abstract String getLockMessageProcessName();
-
- /**
- * Get the repository this receive completes into.
- *
- * @return the repository this receive completes into.
- * @deprecated use {@link ReceivePack#getRepository}
- */
- @Deprecated
- public abstract Repository getRepository();
-
- /**
- * Get the RevWalk instance used by this connection.
- *
- * @return the RevWalk instance used by this connection.
- * @deprecated use {@link ReceivePack#getRevWalk}
- */
- @Deprecated
- public abstract RevWalk getRevWalk();
-
- /**
- * Get refs which were advertised to the client.
- *
- * @return all refs which were advertised to the client, or null if
- * {@link #setAdvertisedRefs(Map, Set)} has not been called yet.
- * @deprecated use {@link ReceivePack#getAdvertisedRefs}
- */
- @Deprecated
- public abstract Map<String, Ref> getAdvertisedRefs();
-
- /**
- * Set the refs advertised by this ReceivePack.
- * <p>
- * Intended to be called from a
- * {@link org.eclipse.jgit.transport.PreReceiveHook}.
- *
- * @param allRefs
- * explicit set of references to claim as advertised by this
- * ReceivePack instance. This overrides any references that may
- * exist in the source repository. The map is passed to the
- * configured {@link #getRefFilter()}. If null, assumes all refs
- * were advertised.
- * @param additionalHaves
- * explicit set of additional haves to claim as advertised. If
- * null, assumes the default set of additional haves from the
- * repository.
- * @deprecated use {@link ReceivePack#setAdvertisedRefs}
- */
- @Deprecated
- public abstract void setAdvertisedRefs(Map<String, Ref> allRefs, Set<ObjectId> additionalHaves);
-
- /**
- * Get objects advertised to the client.
- *
- * @return the set of objects advertised to the as present in this repository,
- * or null if {@link #setAdvertisedRefs(Map, Set)} has not been called
- * yet.
- */
- public final Set<ObjectId> getAdvertisedObjects() {
- return advertisedHaves;
- }
-
- /**
- * Whether this instance will validate all referenced, but not supplied by
- * the client, objects are reachable from another reference.
- *
- * @return true if this instance will validate all referenced, but not
- * supplied by the client, objects are reachable from another
- * reference.
- */
- public boolean isCheckReferencedObjectsAreReachable() {
- return checkReferencedIsReachable;
- }
-
- /**
- * Validate all referenced but not supplied objects are reachable.
- * <p>
- * If enabled, this instance will verify that references to objects not
- * contained within the received pack are already reachable through at least
- * one other reference displayed as part of {@link #getAdvertisedRefs()}.
- * <p>
- * This feature is useful when the application doesn't trust the client to
- * not provide a forged SHA-1 reference to an object, in an attempt to
- * access parts of the DAG that they aren't allowed to see and which have
- * been hidden from them via the configured
- * {@link org.eclipse.jgit.transport.AdvertiseRefsHook} or
- * {@link org.eclipse.jgit.transport.RefFilter}.
- * <p>
- * Enabling this feature may imply at least some, if not all, of the same
- * functionality performed by {@link #setCheckReceivedObjects(boolean)}.
- * Applications are encouraged to enable both features, if desired.
- *
- * @param b
- * {@code true} to enable the additional check.
- */
- public void setCheckReferencedObjectsAreReachable(boolean b) {
- this.checkReferencedIsReachable = b;
- }
-
- /**
- * Whether this class expects a bi-directional pipe opened between the
- * client and itself.
- *
- * @return true if this class expects a bi-directional pipe opened between
- * the client and itself. The default is true.
- */
- public boolean isBiDirectionalPipe() {
- return biDirectionalPipe;
- }
-
- /**
- * Whether this class will assume the socket is a fully bidirectional pipe
- * between the two peers and takes advantage of that by first transmitting
- * the known refs, then waiting to read commands.
- *
- * @param twoWay
- * if true, this class will assume the socket is a fully
- * bidirectional pipe between the two peers and takes advantage
- * of that by first transmitting the known refs, then waiting to
- * read commands. If false, this class assumes it must read the
- * commands before writing output and does not perform the
- * initial advertising.
- */
- public void setBiDirectionalPipe(boolean twoWay) {
- biDirectionalPipe = twoWay;
- }
-
- /**
- * Whether there is data expected after the pack footer.
- *
- * @return {@code true} if there is data expected after the pack footer.
- */
- public boolean isExpectDataAfterPackFooter() {
- return expectDataAfterPackFooter;
- }
-
- /**
- * Whether there is additional data in InputStream after pack.
- *
- * @param e
- * {@code true} if there is additional data in InputStream after
- * pack.
- */
- public void setExpectDataAfterPackFooter(boolean e) {
- expectDataAfterPackFooter = e;
- }
-
- /**
- * Whether this instance will verify received objects are formatted
- * correctly.
- *
- * @return {@code true} if this instance will verify received objects are
- * formatted correctly. Validating objects requires more CPU time on
- * this side of the connection.
- */
- public boolean isCheckReceivedObjects() {
- return objectChecker != null;
- }
-
- /**
- * Whether to enable checking received objects
- *
- * @param check
- * {@code true} to enable checking received objects; false to
- * assume all received objects are valid.
- * @see #setObjectChecker(ObjectChecker)
- */
- public void setCheckReceivedObjects(boolean check) {
- if (check && objectChecker == null)
- setObjectChecker(new ObjectChecker());
- else if (!check && objectChecker != null)
- setObjectChecker(null);
- }
-
- /**
- * Set the object checking instance to verify each received object with
- *
- * @param impl
- * if non-null the object checking instance to verify each
- * received object with; null to disable object checking.
- * @since 3.4
- */
- public void setObjectChecker(ObjectChecker impl) {
- objectChecker = impl;
- }
-
- /**
- * Whether the client can request refs to be created.
- *
- * @return {@code true} if the client can request refs to be created.
- */
- public boolean isAllowCreates() {
- return allowCreates;
- }
-
- /**
- * Whether to permit create ref commands to be processed.
- *
- * @param canCreate
- * {@code true} to permit create ref commands to be processed.
- */
- public void setAllowCreates(boolean canCreate) {
- allowCreates = canCreate;
- }
-
- /**
- * Whether the client can request refs to be deleted.
- *
- * @return {@code true} if the client can request refs to be deleted.
- */
- public boolean isAllowDeletes() {
- return allowAnyDeletes;
- }
-
- /**
- * Whether to permit delete ref commands to be processed.
- *
- * @param canDelete
- * {@code true} to permit delete ref commands to be processed.
- */
- public void setAllowDeletes(boolean canDelete) {
- allowAnyDeletes = canDelete;
- }
-
- /**
- * Whether the client can delete from {@code refs/heads/}.
- *
- * @return {@code true} if the client can delete from {@code refs/heads/}.
- * @since 3.6
- */
- public boolean isAllowBranchDeletes() {
- return allowBranchDeletes;
- }
-
- /**
- * Configure whether to permit deletion of branches from the
- * {@code refs/heads/} namespace.
- *
- * @param canDelete
- * {@code true} to permit deletion of branches from the
- * {@code refs/heads/} namespace.
- * @since 3.6
- */
- public void setAllowBranchDeletes(boolean canDelete) {
- allowBranchDeletes = canDelete;
- }
-
- /**
- * Whether the client can request non-fast-forward updates of a ref,
- * possibly making objects unreachable.
- *
- * @return {@code true} if the client can request non-fast-forward updates
- * of a ref, possibly making objects unreachable.
- */
- public boolean isAllowNonFastForwards() {
- return allowNonFastForwards;
- }
-
- /**
- * Configure whether to permit the client to ask for non-fast-forward
- * updates of an existing ref.
- *
- * @param canRewind
- * {@code true} to permit the client to ask for non-fast-forward
- * updates of an existing ref.
- */
- public void setAllowNonFastForwards(boolean canRewind) {
- allowNonFastForwards = canRewind;
- }
-
- /**
- * Whether the client's commands should be performed as a single atomic
- * transaction.
- *
- * @return {@code true} if the client's commands should be performed as a
- * single atomic transaction.
- * @since 4.4
- */
- public boolean isAtomic() {
- return atomic;
- }
-
- /**
- * Configure whether to perform the client's commands as a single atomic
- * transaction.
- *
- * @param atomic
- * {@code true} to perform the client's commands as a single
- * atomic transaction.
- * @since 4.4
- */
- public void setAtomic(boolean atomic) {
- this.atomic = atomic;
- }
-
- /**
- * Get identity of the user making the changes in the reflog.
- *
- * @return identity of the user making the changes in the reflog.
- */
- public PersonIdent getRefLogIdent() {
- return refLogIdent;
- }
-
- /**
- * Set the identity of the user appearing in the affected reflogs.
- * <p>
- * The timestamp portion of the identity is ignored. A new identity with the
- * current timestamp will be created automatically when the updates occur
- * and the log records are written.
- *
- * @param pi
- * identity of the user. If null the identity will be
- * automatically determined based on the repository
- * configuration.
- */
- public void setRefLogIdent(PersonIdent pi) {
- refLogIdent = pi;
- }
-
- /**
- * Get the hook used while advertising the refs to the client
- *
- * @return the hook used while advertising the refs to the client
- */
- public AdvertiseRefsHook getAdvertiseRefsHook() {
- return advertiseRefsHook;
- }
-
- /**
- * Get the filter used while advertising the refs to the client
- *
- * @return the filter used while advertising the refs to the client
- */
- public RefFilter getRefFilter() {
- return refFilter;
- }
-
- /**
- * Set the hook used while advertising the refs to the client.
- * <p>
- * If the {@link org.eclipse.jgit.transport.AdvertiseRefsHook} chooses to
- * call {@link #setAdvertisedRefs(Map,Set)}, only refs set by this hook
- * <em>and</em> selected by the {@link org.eclipse.jgit.transport.RefFilter}
- * will be shown to the client. Clients may still attempt to create or
- * update a reference not advertised by the configured
- * {@link org.eclipse.jgit.transport.AdvertiseRefsHook}. These attempts
- * should be rejected by a matching
- * {@link org.eclipse.jgit.transport.PreReceiveHook}.
- *
- * @param advertiseRefsHook
- * the hook; may be null to show all refs.
- */
- public void setAdvertiseRefsHook(AdvertiseRefsHook advertiseRefsHook) {
- if (advertiseRefsHook != null)
- this.advertiseRefsHook = advertiseRefsHook;
- else
- this.advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
- }
-
- /**
- * Set the filter used while advertising the refs to the client.
- * <p>
- * Only refs allowed by this filter will be shown to the client. The filter
- * is run against the refs specified by the
- * {@link org.eclipse.jgit.transport.AdvertiseRefsHook} (if applicable).
- *
- * @param refFilter
- * the filter; may be null to show all refs.
- */
- public void setRefFilter(RefFilter refFilter) {
- this.refFilter = refFilter != null ? refFilter : RefFilter.DEFAULT;
- }
-
- /**
- * Get timeout (in seconds) before aborting an IO operation.
- *
- * @return timeout (in seconds) before aborting an IO operation.
- */
- public int getTimeout() {
- return timeout;
- }
-
- /**
- * Set the timeout before willing to abort an IO call.
- *
- * @param seconds
- * number of seconds to wait (with no data transfer occurring)
- * before aborting an IO read or write operation with the
- * connected client.
- */
- public void setTimeout(int seconds) {
- timeout = seconds;
- }
-
- /**
- * Set the maximum number of command bytes to read from the client.
- *
- * @param limit
- * command limit in bytes; if 0 there is no limit.
- * @since 4.7
- */
- public void setMaxCommandBytes(long limit) {
- maxCommandBytes = limit;
- }
-
- /**
- * Set the maximum number of command bytes to discard from the client.
- * <p>
- * Discarding remaining bytes allows this instance to consume the rest of
- * the command block and send a human readable over-limit error via the
- * side-band channel. If the client sends an excessive number of bytes this
- * limit kicks in and the instance disconnects, resulting in a non-specific
- * 'pipe closed', 'end of stream', or similar generic error at the client.
- * <p>
- * When the limit is set to {@code -1} the implementation will default to
- * the larger of {@code 3 * maxCommandBytes} or {@code 3 MiB}.
- *
- * @param limit
- * discard limit in bytes; if 0 there is no limit; if -1 the
- * implementation tries to set a reasonable default.
- * @since 4.7
- */
- public void setMaxCommandDiscardBytes(long limit) {
- maxDiscardBytes = limit;
- }
-
- /**
- * Set the maximum allowed Git object size.
- * <p>
- * If an object is larger than the given size the pack-parsing will throw an
- * exception aborting the receive-pack operation.
- *
- * @param limit
- * the Git object size limit. If zero then there is not limit.
- */
- public void setMaxObjectSizeLimit(long limit) {
- maxObjectSizeLimit = limit;
- }
-
- /**
- * Set the maximum allowed pack size.
- * <p>
- * A pack exceeding this size will be rejected.
- *
- * @param limit
- * the pack size limit, in bytes
- * @since 3.3
- */
- public void setMaxPackSizeLimit(long limit) {
- if (limit < 0)
- throw new IllegalArgumentException(MessageFormat.format(
- JGitText.get().receivePackInvalidLimit, Long.valueOf(limit)));
- maxPackSizeLimit = limit;
- }
-
- /**
- * Check whether the client expects a side-band stream.
- *
- * @return true if the client has advertised a side-band capability, false
- * otherwise.
- * @throws org.eclipse.jgit.transport.RequestNotYetReadException
- * if the client's request has not yet been read from the wire, so
- * we do not know if they expect side-band. Note that the client
- * may have already written the request, it just has not been
- * read.
- */
- public boolean isSideBand() throws RequestNotYetReadException {
- checkRequestWasRead();
- return enabledCapabilities.contains(CAPABILITY_SIDE_BAND_64K);
- }
-
- /**
- * Whether clients may request avoiding noisy progress messages.
- *
- * @return true if clients may request avoiding noisy progress messages.
- * @since 4.0
- */
- public boolean isAllowQuiet() {
- return allowQuiet;
- }
-
- /**
- * Configure if clients may request the server skip noisy messages.
- *
- * @param allow
- * true to allow clients to request quiet behavior; false to
- * refuse quiet behavior and send messages anyway. This may be
- * necessary if processing is slow and the client-server network
- * connection can timeout.
- * @since 4.0
- */
- public void setAllowQuiet(boolean allow) {
- allowQuiet = allow;
- }
-
- /**
- * Whether the server supports receiving push options.
- *
- * @return true if the server supports receiving push options.
- * @since 4.5
- */
- public boolean isAllowPushOptions() {
- return allowPushOptions;
- }
-
- /**
- * Configure if the server supports receiving push options.
- *
- * @param allow
- * true to optionally accept option strings from the client.
- * @since 4.5
- */
- public void setAllowPushOptions(boolean allow) {
- allowPushOptions = allow;
- }
-
- /**
- * True if the client wants less verbose output.
- *
- * @return true if the client has requested the server to be less verbose.
- * @throws org.eclipse.jgit.transport.RequestNotYetReadException
- * if the client's request has not yet been read from the wire,
- * so we do not know if they expect side-band. Note that the
- * client may have already written the request, it just has not
- * been read.
- * @since 4.0
- */
- public boolean isQuiet() throws RequestNotYetReadException {
- checkRequestWasRead();
- return quiet;
- }
-
- /**
- * Set the configuration for push certificate verification.
- *
- * @param cfg
- * new configuration; if this object is null or its {@link
- * SignedPushConfig#getCertNonceSeed()} is null, push certificate
- * verification will be disabled.
- * @since 4.1
- */
- public void setSignedPushConfig(SignedPushConfig cfg) {
- signedPushConfig = cfg;
- }
-
- private PushCertificateParser getPushCertificateParser() {
- if (pushCertificateParser == null) {
- pushCertificateParser = new PushCertificateParser(db, signedPushConfig);
- }
- return pushCertificateParser;
- }
-
- /**
- * Get the user agent of the client.
- * <p>
- * If the client is new enough to use {@code agent=} capability that value
- * will be returned. Older HTTP clients may also supply their version using
- * the HTTP {@code User-Agent} header. The capability overrides the HTTP
- * header if both are available.
- * <p>
- * When an HTTP request has been received this method returns the HTTP
- * {@code User-Agent} header value until capabilities have been parsed.
- *
- * @return user agent supplied by the client. Available only if the client
- * is new enough to advertise its user agent.
- * @since 4.0
- */
- public String getPeerUserAgent() {
- return UserAgent.getAgent(enabledCapabilities, userAgent);
- }
-
- /**
- * Get all of the command received by the current request.
- *
- * @return all of the command received by the current request.
- */
- public List<ReceiveCommand> getAllCommands() {
- return Collections.unmodifiableList(commands);
- }
-
- /**
- * Send an error message to the client.
- * <p>
- * If any error messages are sent before the references are advertised to
- * the client, the errors will be sent instead of the advertisement and the
- * receive operation will be aborted. All clients should receive and display
- * such early stage errors.
- * <p>
- * If the reference advertisements have already been sent, messages are sent
- * in a side channel. If the client doesn't support receiving messages, the
- * message will be discarded, with no other indication to the caller or to
- * the client.
- * <p>
- * {@link org.eclipse.jgit.transport.PreReceiveHook}s should always try to
- * use
- * {@link org.eclipse.jgit.transport.ReceiveCommand#setResult(Result, String)}
- * with a result status of
- * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#REJECTED_OTHER_REASON}
- * to indicate any reasons for rejecting an update. Messages attached to a
- * command are much more likely to be returned to the client.
- *
- * @param what
- * string describing the problem identified by the hook. The
- * string must not end with an LF, and must not contain an LF.
- */
- public void sendError(String what) {
- if (refs == null) {
- if (advertiseError == null)
- advertiseError = new StringBuilder();
- advertiseError.append(what).append('\n');
- } else {
- msgOutWrapper.write(Constants.encode("error: " + what + "\n")); //$NON-NLS-1$ //$NON-NLS-2$
- }
- }
-
- private void fatalError(String msg) {
- if (errOut != null) {
- try {
- errOut.write(Constants.encode(msg));
- errOut.flush();
- } catch (IOException e) {
- // Ignore write failures
- }
- } else {
- sendError(msg);
- }
- }
-
- /**
- * Send a message to the client, if it supports receiving them.
- * <p>
- * If the client doesn't support receiving messages, the message will be
- * discarded, with no other indication to the caller or to the client.
- *
- * @param what
- * string describing the problem identified by the hook. The
- * string must not end with an LF, and must not contain an LF.
- */
- public void sendMessage(String what) {
- msgOutWrapper.write(Constants.encode(what + "\n")); //$NON-NLS-1$
- }
-
- /**
- * Get an underlying stream for sending messages to the client.
- *
- * @return an underlying stream for sending messages to the client.
- */
- public OutputStream getMessageOutputStream() {
- return msgOutWrapper;
- }
-
- /**
- * Get the size of the received pack file including the index size.
- *
- * This can only be called if the pack is already received.
- *
- * @return the size of the received pack including index size
- * @throws java.lang.IllegalStateException
- * if called before the pack has been received
- * @since 3.3
- */
- public long getPackSize() {
- if (packSize != null)
- return packSize.longValue();
- throw new IllegalStateException(JGitText.get().packSizeNotSetYet);
- }
-
- /**
- * Get the commits from the client's shallow file.
- *
- * @return if the client is a shallow repository, the list of edge commits
- * that define the client's shallow boundary. Empty set if the client
- * is earlier than Git 1.9, or is a full clone.
- * @since 3.5
- */
- protected Set<ObjectId> getClientShallowCommits() {
- return clientShallowCommits;
- }
-
- /**
- * Whether any commands to be executed have been read.
- *
- * @return {@code true} if any commands to be executed have been read.
- */
- protected boolean hasCommands() {
- return !commands.isEmpty();
- }
-
- /**
- * Whether an error occurred that should be advertised.
- *
- * @return true if an error occurred that should be advertised.
- */
- protected boolean hasError() {
- return advertiseError != null;
- }
-
- /**
- * Initialize the instance with the given streams.
- *
- * @param input
- * raw input to read client commands and pack data from. Caller
- * must ensure the input is buffered, otherwise read performance
- * may suffer.
- * @param output
- * response back to the Git network client. Caller must ensure
- * the output is buffered, otherwise write performance may
- * suffer.
- * @param messages
- * secondary "notice" channel to send additional messages out
- * through. When run over SSH this should be tied back to the
- * standard error channel of the command execution. For most
- * other network connections this should be null.
- */
- protected void init(final InputStream input, final OutputStream output,
- final OutputStream messages) {
- origOut = output;
- rawIn = input;
- rawOut = output;
- msgOut = messages;
-
- if (timeout > 0) {
- final Thread caller = Thread.currentThread();
- timer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$
- timeoutIn = new TimeoutInputStream(rawIn, timer);
- TimeoutOutputStream o = new TimeoutOutputStream(rawOut, timer);
- timeoutIn.setTimeout(timeout * 1000);
- o.setTimeout(timeout * 1000);
- rawIn = timeoutIn;
- rawOut = o;
- }
-
- pckIn = new PacketLineIn(rawIn);
- pckOut = new PacketLineOut(rawOut);
- pckOut.setFlushOnEnd(false);
-
- enabledCapabilities = new HashSet<>();
- commands = new ArrayList<>();
- }
-
- /**
- * Get advertised refs, or the default if not explicitly advertised.
- *
- * @return advertised refs, or the default if not explicitly advertised.
- */
- protected Map<String, Ref> getAdvertisedOrDefaultRefs() {
- if (refs == null)
- setAdvertisedRefs(null, null);
- return refs;
- }
-
- /**
- * Receive a pack from the stream and check connectivity if necessary.
- *
- * @throws java.io.IOException
- * an error occurred during unpacking or connectivity checking.
- */
- protected void receivePackAndCheckConnectivity() throws IOException {
- receivePack();
- if (needCheckConnectivity()) {
- checkSubmodules();
- checkConnectivity();
- }
- parser = null;
- }
-
- /**
- * Unlock the pack written by this object.
- *
- * @throws java.io.IOException
- * the pack could not be unlocked.
- */
- protected void unlockPack() throws IOException {
- if (packLock != null) {
- packLock.unlock();
- packLock = null;
- }
- }
-
- /**
- * Generate an advertisement of available refs and capabilities.
- *
- * @param adv
- * the advertisement formatter.
- * @throws java.io.IOException
- * the formatter failed to write an advertisement.
- * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException
- * the hook denied advertisement.
- */
- public void sendAdvertisedRefs(RefAdvertiser adv)
- throws IOException, ServiceMayNotContinueException {
- if (advertiseError != null) {
- adv.writeOne("ERR " + advertiseError); //$NON-NLS-1$
- return;
- }
-
- try {
- advertiseRefsHook.advertiseRefs(this);
- } catch (ServiceMayNotContinueException fail) {
- if (fail.getMessage() != null) {
- adv.writeOne("ERR " + fail.getMessage()); //$NON-NLS-1$
- fail.setOutput();
- }
- throw fail;
- }
-
- adv.init(db);
- adv.advertiseCapability(CAPABILITY_SIDE_BAND_64K);
- adv.advertiseCapability(CAPABILITY_DELETE_REFS);
- adv.advertiseCapability(CAPABILITY_REPORT_STATUS);
- if (allowQuiet)
- adv.advertiseCapability(CAPABILITY_QUIET);
- String nonce = getPushCertificateParser().getAdvertiseNonce();
- if (nonce != null) {
- adv.advertiseCapability(nonce);
- }
- if (db.getRefDatabase().performsAtomicTransactions())
- adv.advertiseCapability(CAPABILITY_ATOMIC);
- if (allowOfsDelta)
- adv.advertiseCapability(CAPABILITY_OFS_DELTA);
- if (allowPushOptions) {
- adv.advertiseCapability(CAPABILITY_PUSH_OPTIONS);
- }
- adv.advertiseCapability(OPTION_AGENT, UserAgent.get());
- adv.send(getAdvertisedOrDefaultRefs().values());
- for (ObjectId obj : advertisedHaves)
- adv.advertiseHave(obj);
- if (adv.isEmpty())
- adv.advertiseId(ObjectId.zeroId(), "capabilities^{}"); //$NON-NLS-1$
- adv.end();
- }
-
- /**
- * Returns the statistics on the received pack if available. This should be
- * called after {@link #receivePack} is called.
- *
- * @return ReceivedPackStatistics
- * @since 4.6
- */
- @Nullable
- public ReceivedPackStatistics getReceivedPackStatistics() {
- return stats;
- }
-
- /**
- * Receive a list of commands from the input.
- *
- * @throws java.io.IOException
- */
- protected void recvCommands() throws IOException {
- PacketLineIn pck = maxCommandBytes > 0
- ? new PacketLineIn(rawIn, maxCommandBytes)
- : pckIn;
- PushCertificateParser certParser = getPushCertificateParser();
- boolean firstPkt = true;
- try {
- for (;;) {
- String line;
- try {
- line = pck.readString();
- } catch (EOFException eof) {
- if (commands.isEmpty())
- return;
- throw eof;
- }
- if (PacketLineIn.isEnd(line)) {
- break;
- }
-
- if (line.length() >= 48 && line.startsWith("shallow ")) { //$NON-NLS-1$
- parseShallow(line.substring(8, 48));
- continue;
- }
-
- if (firstPkt) {
- firstPkt = false;
- FirstCommand firstLine = FirstCommand.fromLine(line);
- enabledCapabilities = firstLine.getCapabilities();
- line = firstLine.getLine();
- enableCapabilities();
-
- if (line.equals(GitProtocolConstants.OPTION_PUSH_CERT)) {
- certParser.receiveHeader(pck, !isBiDirectionalPipe());
- continue;
- }
- }
-
- if (line.equals(PushCertificateParser.BEGIN_SIGNATURE)) {
- certParser.receiveSignature(pck);
- continue;
- }
-
- ReceiveCommand cmd = parseCommand(line);
- if (cmd.getRefName().equals(Constants.HEAD)) {
- cmd.setResult(Result.REJECTED_CURRENT_BRANCH);
- } else {
- cmd.setRef(refs.get(cmd.getRefName()));
- }
- commands.add(cmd);
- if (certParser.enabled()) {
- certParser.addCommand(cmd);
- }
- }
- pushCert = certParser.build();
- if (hasCommands()) {
- readPostCommands(pck);
- }
- } catch (PackProtocolException e) {
- discardCommands();
- fatalError(e.getMessage());
- throw e;
- } catch (InputOverLimitIOException e) {
- String msg = JGitText.get().tooManyCommands;
- discardCommands();
- fatalError(msg);
- throw new PackProtocolException(msg);
- }
- }
-
- private void discardCommands() {
- if (sideBand) {
- long max = maxDiscardBytes;
- if (max < 0) {
- max = Math.max(3 * maxCommandBytes, 3L << 20);
- }
- try {
- new PacketLineIn(rawIn, max).discardUntilEnd();
- } catch (IOException e) {
- // Ignore read failures attempting to discard.
- }
- }
- }
-
- private void parseShallow(String idStr) throws PackProtocolException {
- ObjectId id;
- try {
- id = ObjectId.fromString(idStr);
- } catch (InvalidObjectIdException e) {
- throw new PackProtocolException(e.getMessage(), e);
- }
- clientShallowCommits.add(id);
- }
-
- static ReceiveCommand parseCommand(String line) throws PackProtocolException {
- if (line == null || line.length() < 83) {
- throw new PackProtocolException(
- JGitText.get().errorInvalidProtocolWantedOldNewRef);
- }
- String oldStr = line.substring(0, 40);
- String newStr = line.substring(41, 81);
- ObjectId oldId, newId;
- try {
- oldId = ObjectId.fromString(oldStr);
- newId = ObjectId.fromString(newStr);
- } catch (InvalidObjectIdException e) {
- throw new PackProtocolException(
- JGitText.get().errorInvalidProtocolWantedOldNewRef, e);
- }
- String name = line.substring(82);
- if (!Repository.isValidRefName(name)) {
- throw new PackProtocolException(
- JGitText.get().errorInvalidProtocolWantedOldNewRef);
- }
- return new ReceiveCommand(oldId, newId, name);
- }
-
- /**
- * @param in
- * request stream.
- * @throws IOException
- * request line cannot be read.
- */
- void readPostCommands(PacketLineIn in) throws IOException {
- // Do nothing by default.
- }
-
- /**
- * Enable capabilities based on a previously read capabilities line.
- */
- protected void enableCapabilities() {
- sideBand = isCapabilityEnabled(CAPABILITY_SIDE_BAND_64K);
- quiet = allowQuiet && isCapabilityEnabled(CAPABILITY_QUIET);
- if (sideBand) {
- OutputStream out = rawOut;
-
- rawOut = new SideBandOutputStream(CH_DATA, MAX_BUF, out);
- msgOut = new SideBandOutputStream(CH_PROGRESS, MAX_BUF, out);
- errOut = new SideBandOutputStream(CH_ERROR, MAX_BUF, out);
-
- pckOut = new PacketLineOut(rawOut);
- pckOut.setFlushOnEnd(false);
- }
- }
-
- /**
- * Check if the peer requested a capability.
- *
- * @param name
- * protocol name identifying the capability.
- * @return true if the peer requested the capability to be enabled.
- */
- protected boolean isCapabilityEnabled(String name) {
- return enabledCapabilities.contains(name);
- }
-
- void checkRequestWasRead() {
- if (enabledCapabilities == null)
- throw new RequestNotYetReadException();
- }
-
- /**
- * Whether a pack is expected based on the list of commands.
- *
- * @return {@code true} if a pack is expected based on the list of commands.
- */
- protected boolean needPack() {
- for (ReceiveCommand cmd : commands) {
- if (cmd.getType() != ReceiveCommand.Type.DELETE)
- return true;
- }
- return false;
- }
-
- /**
- * Receive a pack from the input and store it in the repository.
- *
- * @throws IOException
- * an error occurred reading or indexing the pack.
- */
- private void receivePack() throws IOException {
- // It might take the client a while to pack the objects it needs
- // to send to us. We should increase our timeout so we don't
- // abort while the client is computing.
- //
- if (timeoutIn != null)
- timeoutIn.setTimeout(10 * timeout * 1000);
-
- ProgressMonitor receiving = NullProgressMonitor.INSTANCE;
- ProgressMonitor resolving = NullProgressMonitor.INSTANCE;
- if (sideBand && !quiet)
- resolving = new SideBandProgressMonitor(msgOut);
-
- try (ObjectInserter ins = db.newObjectInserter()) {
- String lockMsg = "jgit receive-pack"; //$NON-NLS-1$
- if (getRefLogIdent() != null)
- lockMsg += " from " + getRefLogIdent().toExternalString(); //$NON-NLS-1$
-
- parser = ins.newPackParser(packInputStream());
- parser.setAllowThin(true);
- parser.setNeedNewObjectIds(checkReferencedIsReachable);
- parser.setNeedBaseObjectIds(checkReferencedIsReachable);
- parser.setCheckEofAfterPackFooter(!biDirectionalPipe
- && !isExpectDataAfterPackFooter());
- parser.setExpectDataAfterPackFooter(isExpectDataAfterPackFooter());
- parser.setObjectChecker(objectChecker);
- parser.setLockMessage(lockMsg);
- parser.setMaxObjectSizeLimit(maxObjectSizeLimit);
- packLock = parser.parse(receiving, resolving);
- packSize = Long.valueOf(parser.getPackSize());
- stats = parser.getReceivedPackStatistics();
- ins.flush();
- }
-
- if (timeoutIn != null)
- timeoutIn.setTimeout(timeout * 1000);
- }
-
- private InputStream packInputStream() {
- InputStream packIn = rawIn;
- if (maxPackSizeLimit >= 0) {
- packIn = new LimitedInputStream(packIn, maxPackSizeLimit) {
- @Override
- protected void limitExceeded() throws TooLargePackException {
- throw new TooLargePackException(limit);
- }
- };
- }
- return packIn;
- }
-
- private boolean needCheckConnectivity() {
- return isCheckReceivedObjects()
- || isCheckReferencedObjectsAreReachable()
- || !getClientShallowCommits().isEmpty();
- }
-
- private void checkSubmodules()
- throws IOException {
- ObjectDatabase odb = db.getObjectDatabase();
- if (objectChecker == null) {
- return;
- }
- for (GitmoduleEntry entry : objectChecker.getGitsubmodules()) {
- AnyObjectId blobId = entry.getBlobId();
- ObjectLoader blob = odb.open(blobId, Constants.OBJ_BLOB);
-
- try {
- SubmoduleValidator.assertValidGitModulesFile(
- new String(blob.getBytes(), UTF_8));
- } catch (LargeObjectException | SubmoduleValidationException e) {
- throw new IOException(e);
- }
- }
- }
-
- private void checkConnectivity() throws IOException {
- ObjectIdSubclassMap<ObjectId> baseObjects = null;
- ObjectIdSubclassMap<ObjectId> providedObjects = null;
- ProgressMonitor checking = NullProgressMonitor.INSTANCE;
- if (sideBand && !quiet) {
- SideBandProgressMonitor m = new SideBandProgressMonitor(msgOut);
- m.setDelayStart(750, TimeUnit.MILLISECONDS);
- checking = m;
- }
-
- if (checkReferencedIsReachable) {
- baseObjects = parser.getBaseObjectIds();
- providedObjects = parser.getNewObjectIds();
- }
- parser = null;
-
- try (ObjectWalk ow = new ObjectWalk(db)) {
- if (baseObjects != null) {
- ow.sort(RevSort.TOPO);
- if (!baseObjects.isEmpty())
- ow.sort(RevSort.BOUNDARY, true);
- }
-
- for (ReceiveCommand cmd : commands) {
- if (cmd.getResult() != Result.NOT_ATTEMPTED)
- continue;
- if (cmd.getType() == ReceiveCommand.Type.DELETE)
- continue;
- ow.markStart(ow.parseAny(cmd.getNewId()));
- }
- for (ObjectId have : advertisedHaves) {
- RevObject o = ow.parseAny(have);
- ow.markUninteresting(o);
-
- if (baseObjects != null && !baseObjects.isEmpty()) {
- o = ow.peel(o);
- if (o instanceof RevCommit)
- o = ((RevCommit) o).getTree();
- if (o instanceof RevTree)
- ow.markUninteresting(o);
- }
- }
-
- checking.beginTask(JGitText.get().countingObjects,
- ProgressMonitor.UNKNOWN);
- RevCommit c;
- while ((c = ow.next()) != null) {
- checking.update(1);
- if (providedObjects != null //
- && !c.has(RevFlag.UNINTERESTING) //
- && !providedObjects.contains(c))
- throw new MissingObjectException(c, Constants.TYPE_COMMIT);
- }
-
- RevObject o;
- while ((o = ow.nextObject()) != null) {
- checking.update(1);
- if (o.has(RevFlag.UNINTERESTING))
- continue;
-
- if (providedObjects != null) {
- if (providedObjects.contains(o))
- continue;
- else
- throw new MissingObjectException(o, o.getType());
- }
-
- if (o instanceof RevBlob && !db.getObjectDatabase().has(o))
- throw new MissingObjectException(o, Constants.TYPE_BLOB);
- }
- checking.endTask();
-
- if (baseObjects != null) {
- for (ObjectId id : baseObjects) {
- o = ow.parseAny(id);
- if (!o.has(RevFlag.UNINTERESTING))
- throw new MissingObjectException(o, o.getType());
- }
- }
- }
- }
-
- /**
- * Validate the command list.
- */
- protected void validateCommands() {
- for (ReceiveCommand cmd : commands) {
- final Ref ref = cmd.getRef();
- if (cmd.getResult() != Result.NOT_ATTEMPTED)
- continue;
-
- if (cmd.getType() == ReceiveCommand.Type.DELETE) {
- if (!isAllowDeletes()) {
- // Deletes are not supported on this repository.
- cmd.setResult(Result.REJECTED_NODELETE);
- continue;
- }
- if (!isAllowBranchDeletes()
- && ref.getName().startsWith(Constants.R_HEADS)) {
- // Branches cannot be deleted, but other refs can.
- cmd.setResult(Result.REJECTED_NODELETE);
- continue;
- }
- }
-
- if (cmd.getType() == ReceiveCommand.Type.CREATE) {
- if (!isAllowCreates()) {
- cmd.setResult(Result.REJECTED_NOCREATE);
- continue;
- }
-
- if (ref != null && !isAllowNonFastForwards()) {
- // Creation over an existing ref is certainly not going
- // to be a fast-forward update. We can reject it early.
- //
- cmd.setResult(Result.REJECTED_NONFASTFORWARD);
- continue;
- }
-
- if (ref != null) {
- // A well behaved client shouldn't have sent us a
- // create command for a ref we advertised to it.
- //
- cmd.setResult(Result.REJECTED_OTHER_REASON,
- JGitText.get().refAlreadyExists);
- continue;
- }
- }
-
- if (cmd.getType() == ReceiveCommand.Type.DELETE && ref != null) {
- ObjectId id = ref.getObjectId();
- if (id == null) {
- id = ObjectId.zeroId();
- }
- if (!ObjectId.zeroId().equals(cmd.getOldId())
- && !id.equals(cmd.getOldId())) {
- // Delete commands can be sent with the old id matching our
- // advertised value, *OR* with the old id being 0{40}. Any
- // other requested old id is invalid.
- //
- cmd.setResult(Result.REJECTED_OTHER_REASON,
- JGitText.get().invalidOldIdSent);
- continue;
- }
- }
-
- if (cmd.getType() == ReceiveCommand.Type.UPDATE) {
- if (ref == null) {
- // The ref must have been advertised in order to be updated.
- //
- cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().noSuchRef);
- continue;
- }
- ObjectId id = ref.getObjectId();
- if (id == null) {
- // We cannot update unborn branch
- cmd.setResult(Result.REJECTED_OTHER_REASON,
- JGitText.get().cannotUpdateUnbornBranch);
- continue;
- }
-
- if (!id.equals(cmd.getOldId())) {
- // A properly functioning client will send the same
- // object id we advertised.
- //
- cmd.setResult(Result.REJECTED_OTHER_REASON,
- JGitText.get().invalidOldIdSent);
- continue;
- }
-
- // Is this possibly a non-fast-forward style update?
- //
- RevObject oldObj, newObj;
- try {
- oldObj = walk.parseAny(cmd.getOldId());
- } catch (IOException e) {
- cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd
- .getOldId().name());
- continue;
- }
-
- try {
- newObj = walk.parseAny(cmd.getNewId());
- } catch (IOException e) {
- cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd
- .getNewId().name());
- continue;
- }
-
- if (oldObj instanceof RevCommit && newObj instanceof RevCommit) {
- try {
- if (walk.isMergedInto((RevCommit) oldObj,
- (RevCommit) newObj))
- cmd.setTypeFastForwardUpdate();
- else
- cmd.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD);
- } catch (MissingObjectException e) {
- cmd.setResult(Result.REJECTED_MISSING_OBJECT, e
- .getMessage());
- } catch (IOException e) {
- cmd.setResult(Result.REJECTED_OTHER_REASON);
- }
- } else {
- cmd.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD);
- }
-
- if (cmd.getType() == ReceiveCommand.Type.UPDATE_NONFASTFORWARD
- && !isAllowNonFastForwards()) {
- cmd.setResult(Result.REJECTED_NONFASTFORWARD);
- continue;
- }
- }
-
- if (!cmd.getRefName().startsWith(Constants.R_REFS)
- || !Repository.isValidRefName(cmd.getRefName())) {
- cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().funnyRefname);
- }
- }
- }
-
- /**
- * Whether any commands have been rejected so far.
- *
- * @return if any commands have been rejected so far.
- * @since 3.6
- */
- protected boolean anyRejects() {
- for (ReceiveCommand cmd : commands) {
- if (cmd.getResult() != Result.NOT_ATTEMPTED && cmd.getResult() != Result.OK)
- return true;
- }
- return false;
- }
-
- /**
- * Set the result to fail for any command that was not processed yet.
- *
- * @since 3.6
- */
- protected void failPendingCommands() {
- ReceiveCommand.abort(commands);
- }
-
- /**
- * Filter the list of commands according to result.
- *
- * @param want
- * desired status to filter by.
- * @return a copy of the command list containing only those commands with the
- * desired status.
- */
- protected List<ReceiveCommand> filterCommands(Result want) {
- return ReceiveCommand.filter(commands, want);
- }
-
- /**
- * Execute commands to update references.
- */
- protected void executeCommands() {
- List<ReceiveCommand> toApply = filterCommands(Result.NOT_ATTEMPTED);
- if (toApply.isEmpty())
- return;
-
- ProgressMonitor updating = NullProgressMonitor.INSTANCE;
- if (sideBand) {
- SideBandProgressMonitor pm = new SideBandProgressMonitor(msgOut);
- pm.setDelayStart(250, TimeUnit.MILLISECONDS);
- updating = pm;
- }
-
- BatchRefUpdate batch = db.getRefDatabase().newBatchUpdate();
- batch.setAllowNonFastForwards(isAllowNonFastForwards());
- batch.setAtomic(isAtomic());
- batch.setRefLogIdent(getRefLogIdent());
- batch.setRefLogMessage("push", true); //$NON-NLS-1$
- batch.addCommand(toApply);
- try {
- batch.setPushCertificate(getPushCertificate());
- batch.execute(walk, updating);
- } catch (IOException err) {
- for (ReceiveCommand cmd : toApply) {
- if (cmd.getResult() == Result.NOT_ATTEMPTED)
- cmd.reject(err);
- }
- }
- }
-
- /**
- * Send a status report.
- *
- * @param forClient
- * true if this report is for a Git client, false if it is for an
- * end-user.
- * @param unpackError
- * an error that occurred during unpacking, or {@code null}
- * @param out
- * the reporter for sending the status strings.
- * @throws java.io.IOException
- * an error occurred writing the status report.
- */
- protected void sendStatusReport(final boolean forClient,
- final Throwable unpackError, final Reporter out) throws IOException {
- if (unpackError != null) {
- out.sendString("unpack error " + unpackError.getMessage()); //$NON-NLS-1$
- if (forClient) {
- for (ReceiveCommand cmd : commands) {
- out.sendString("ng " + cmd.getRefName() //$NON-NLS-1$
- + " n/a (unpacker error)"); //$NON-NLS-1$
- }
- }
- return;
- }
-
- if (forClient)
- out.sendString("unpack ok"); //$NON-NLS-1$
- for (ReceiveCommand cmd : commands) {
- if (cmd.getResult() == Result.OK) {
- if (forClient)
- out.sendString("ok " + cmd.getRefName()); //$NON-NLS-1$
- continue;
- }
-
- final StringBuilder r = new StringBuilder();
- if (forClient)
- r.append("ng ").append(cmd.getRefName()).append(" "); //$NON-NLS-1$ //$NON-NLS-2$
- else
- r.append(" ! [rejected] ").append(cmd.getRefName()).append(" ("); //$NON-NLS-1$ //$NON-NLS-2$
-
- switch (cmd.getResult()) {
- case NOT_ATTEMPTED:
- r.append("server bug; ref not processed"); //$NON-NLS-1$
- break;
-
- case REJECTED_NOCREATE:
- r.append("creation prohibited"); //$NON-NLS-1$
- break;
-
- case REJECTED_NODELETE:
- r.append("deletion prohibited"); //$NON-NLS-1$
- break;
-
- case REJECTED_NONFASTFORWARD:
- r.append("non-fast forward"); //$NON-NLS-1$
- break;
-
- case REJECTED_CURRENT_BRANCH:
- r.append("branch is currently checked out"); //$NON-NLS-1$
- break;
-
- case REJECTED_MISSING_OBJECT:
- if (cmd.getMessage() == null)
- r.append("missing object(s)"); //$NON-NLS-1$
- else if (cmd.getMessage().length() == Constants.OBJECT_ID_STRING_LENGTH) {
- r.append("object "); //$NON-NLS-1$
- r.append(cmd.getMessage());
- r.append(" missing"); //$NON-NLS-1$
- } else
- r.append(cmd.getMessage());
- break;
-
- case REJECTED_OTHER_REASON:
- if (cmd.getMessage() == null)
- r.append("unspecified reason"); //$NON-NLS-1$
- else
- r.append(cmd.getMessage());
- break;
-
- case LOCK_FAILURE:
- r.append("failed to lock"); //$NON-NLS-1$
- break;
-
- case OK:
- // We shouldn't have reached this case (see 'ok' case above).
- continue;
- }
- if (!forClient)
- r.append(")"); //$NON-NLS-1$
- out.sendString(r.toString());
- }
- }
-
- /**
- * Close and flush (if necessary) the underlying streams.
- *
- * @throws java.io.IOException
- */
- protected void close() throws IOException {
- if (sideBand) {
- // If we are using side band, we need to send a final
- // flush-pkt to tell the remote peer the side band is
- // complete and it should stop decoding. We need to
- // use the original output stream as rawOut is now the
- // side band data channel.
- //
- ((SideBandOutputStream) msgOut).flushBuffer();
- ((SideBandOutputStream) rawOut).flushBuffer();
-
- PacketLineOut plo = new PacketLineOut(origOut);
- plo.setFlushOnEnd(false);
- plo.end();
- }
-
- if (biDirectionalPipe) {
- // If this was a native git connection, flush the pipe for
- // the caller. For smart HTTP we don't do this flush and
- // instead let the higher level HTTP servlet code do it.
- //
- if (!sideBand && msgOut != null)
- msgOut.flush();
- rawOut.flush();
- }
- }
-
- /**
- * Release any resources used by this object.
- *
- * @throws java.io.IOException
- * the pack could not be unlocked.
- */
- protected void release() throws IOException {
- walk.close();
- unlockPack();
- timeoutIn = null;
- rawIn = null;
- rawOut = null;
- msgOut = null;
- pckIn = null;
- pckOut = null;
- refs = null;
- // Keep the capabilities. If responses are sent after this release
- // we need to remember at least whether sideband communication has to be
- // used
- commands = null;
- if (timer != null) {
- try {
- timer.terminate();
- } finally {
- timer = null;
- }
- }
- }
-
- /** Interface for reporting status messages. */
- static abstract class Reporter {
- abstract void sendString(String s) throws IOException;
- }
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProviderUserInfo.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProviderUserInfo.java
index d901021788..6325a23530 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProviderUserInfo.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProviderUserInfo.java
@@ -107,10 +107,9 @@ public class CredentialsProviderUserInfo implements UserInfo,
if (provider.get(uri, v)) {
passphrase = v.getValue();
return true;
- } else {
- passphrase = null;
- return false;
}
+ passphrase = null;
+ return false;
}
/** {@inheritDoc} */
@@ -120,10 +119,9 @@ public class CredentialsProviderUserInfo implements UserInfo,
if (provider.get(uri, p)) {
password = new String(p.getValue());
return true;
- } else {
- password = null;
- return false;
}
+ password = null;
+ return false;
}
private CredentialItem.StringType newPrompt(String msg) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
index e3c0bc629a..2fd074674d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
@@ -280,7 +280,7 @@ public final class GitProtocolConstants {
*/
public static final String COMMAND_FETCH = "fetch"; //$NON-NLS-1$
- static enum MultiAck {
+ enum MultiAck {
OFF, CONTINUE, DETAILED;
}
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 01f6fec7e4..72e95e6ba3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java
@@ -135,9 +135,8 @@ public class HMACSHA1NonceGenerator implements NonceGenerator {
if (nonceStampSlop <= slop) {
return NonceStatus.OK;
- } else {
- return NonceStatus.SLOP;
}
+ return NonceStatus.SLOP;
}
private static final String HEX = "0123456789ABCDEF"; //$NON-NLS-1$
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 2b2795fefb..a9cd677667 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
@@ -103,7 +103,7 @@ public abstract class PackParser {
private static final int BUFFER_SIZE = 8192;
/** Location data is being obtained from. */
- public static enum Source {
+ public enum Source {
/** Data is read from the incoming stream. */
INPUT,
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 d73e1939a6..bbdd55583c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
@@ -95,7 +95,7 @@ public class PacketLineIn {
@Deprecated
public static final String DELIM = new StringBuilder(0).toString(); /* must not string pool */
- static enum AckNackResult {
+ enum AckNackResult {
/** NAK */
NAK,
/** ACK */
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
index 14ccddfb61..c2f69e20be 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
@@ -215,7 +215,7 @@ final class ProtocolV2Parser {
&& line2.equals(OPTION_SIDEBAND_ALL)) {
reqBuilder.setSidebandAll(true);
} else if (line2.startsWith("packfile-uris ")) { //$NON-NLS-1$
- for (String s : line2.substring(14).split(",")) {
+ for (String s : line2.substring(14).split(",")) { //$NON-NLS-1$
reqBuilder.addPackfileUriProtocol(s);
}
} else {
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 89c1a93788..3653879c7d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateParser.java
@@ -43,7 +43,7 @@
package org.eclipse.jgit.transport;
-import static org.eclipse.jgit.transport.BaseReceivePack.parseCommand;
+import static org.eclipse.jgit.transport.ReceivePack.parseCommand;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_CERT;
import java.io.EOFException;
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 a9a995cd3f..d58bc6953b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
@@ -65,14 +65,14 @@ import org.eclipse.jgit.revwalk.RevWalk;
/**
* A command being processed by
- * {@link org.eclipse.jgit.transport.BaseReceivePack}.
+ * {@link org.eclipse.jgit.transport.ReceivePack}.
* <p>
* This command instance roughly translates to the server side representation of
* the {@link org.eclipse.jgit.transport.RemoteRefUpdate} created by the client.
*/
public class ReceiveCommand {
/** Type of operation requested. */
- public static enum Type {
+ public enum Type {
/** Create a new ref; the ref must not already exist. */
CREATE,
@@ -98,7 +98,7 @@ public class ReceiveCommand {
}
/** Result of the update command. */
- public static enum Result {
+ public enum Result {
/** The command has not yet been attempted by the server. */
NOT_ATTEMPTED,
@@ -290,7 +290,7 @@ public class ReceiveCommand {
/**
* Create a new command for
- * {@link org.eclipse.jgit.transport.BaseReceivePack}.
+ * {@link org.eclipse.jgit.transport.ReceivePack}.
*
* @param oldId
* the expected old object id; must not be null. Use
@@ -334,7 +334,7 @@ public class ReceiveCommand {
/**
* Create a new command for
- * {@link org.eclipse.jgit.transport.BaseReceivePack}.
+ * {@link org.eclipse.jgit.transport.ReceivePack}.
*
* @param oldId
* the old object id; must not be null. Use
@@ -768,9 +768,9 @@ public class ReceiveCommand {
*
* @param rp
* receive-pack session.
- * @since 2.0
+ * @since 5.6
*/
- public void execute(BaseReceivePack rp) {
+ public void execute(ReceivePack rp) {
try {
String expTarget = getOldSymref();
boolean detach = getNewSymref() != null
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 d6adf1e0d8..fab9890c7c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
@@ -43,36 +43,255 @@
package org.eclipse.jgit.transport;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC;
+import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_DELETE_REFS;
+import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_OFS_DELTA;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_OPTIONS;
+import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_QUIET;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS;
+import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SIDE_BAND_64K;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
+import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA;
+import static org.eclipse.jgit.transport.SideBandOutputStream.CH_ERROR;
+import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS;
+import static org.eclipse.jgit.transport.SideBandOutputStream.MAX_BUF;
+import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.InvalidObjectIdException;
+import org.eclipse.jgit.errors.LargeObjectException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.PackProtocolException;
+import org.eclipse.jgit.errors.TooLargePackException;
import org.eclipse.jgit.errors.UnpackException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.file.PackLock;
+import org.eclipse.jgit.internal.submodule.SubmoduleValidator;
+import org.eclipse.jgit.internal.submodule.SubmoduleValidator.SubmoduleValidationException;
+import org.eclipse.jgit.internal.transport.parser.FirstCommand;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.GitmoduleEntry;
import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectChecker;
+import org.eclipse.jgit.lib.ObjectDatabase;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdSubclassMap;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.ObjectWalk;
+import org.eclipse.jgit.revwalk.RevBlob;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevSort;
+import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.PacketLineIn.InputOverLimitIOException;
import org.eclipse.jgit.transport.ReceiveCommand.Result;
import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
+import org.eclipse.jgit.util.io.InterruptTimer;
+import org.eclipse.jgit.util.io.LimitedInputStream;
+import org.eclipse.jgit.util.io.TimeoutInputStream;
+import org.eclipse.jgit.util.io.TimeoutOutputStream;
/**
* Implements the server side of a push connection, receiving objects.
*/
-public class ReceivePack extends BaseReceivePack {
+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() {
+ return command.getCapabilities();
+ }
+ }
+
+ /** Database we write the stored objects into. */
+ private final Repository db;
+
+ /** Revision traversal support over {@link #db}. */
+ private final RevWalk walk;
+
+ /**
+ * Is the client connection a bi-directional socket or pipe?
+ * <p>
+ * If true, this class assumes it can perform multiple read and write cycles
+ * with the client over the input and output streams. This matches the
+ * functionality available with a standard TCP/IP connection, or a local
+ * operating system or in-memory pipe.
+ * <p>
+ * If false, this class runs in a read everything then output results mode,
+ * making it suitable for single round-trip systems RPCs such as HTTP.
+ */
+ private boolean biDirectionalPipe = true;
+
+ /** Expecting data after the pack footer */
+ private boolean expectDataAfterPackFooter;
+
+ /** Should an incoming transfer validate objects? */
+ private ObjectChecker objectChecker;
+
+ /** Should an incoming transfer permit create requests? */
+ private boolean allowCreates;
+
+ /** Should an incoming transfer permit delete requests? */
+ private boolean allowAnyDeletes;
+
+ private boolean allowBranchDeletes;
+
+ /** Should an incoming transfer permit non-fast-forward requests? */
+ private boolean allowNonFastForwards;
+
+ /** Should an incoming transfer permit push options? **/
+ private boolean allowPushOptions;
+
+ /**
+ * Should the requested ref updates be performed as a single atomic
+ * transaction?
+ */
+ private boolean atomic;
+
+ private boolean allowOfsDelta;
+
+ private boolean allowQuiet = true;
+
+ /** Identity to record action as within the reflog. */
+ private PersonIdent refLogIdent;
+
+ /** Hook used while advertising the refs to the client. */
+ private AdvertiseRefsHook advertiseRefsHook;
+
+ /** Filter used while advertising the refs to the client. */
+ private RefFilter refFilter;
+
+ /** Timeout in seconds to wait for client interaction. */
+ private int timeout;
+
+ /** Timer to manage {@link #timeout}. */
+ private InterruptTimer timer;
+
+ private TimeoutInputStream timeoutIn;
+
+ // Original stream passed to init(), since rawOut may be wrapped in a
+ // sideband.
+ private OutputStream origOut;
+
+ /** Raw input stream. */
+ private InputStream rawIn;
+
+ /** Raw output stream. */
+ private OutputStream rawOut;
+
+ /** Optional message output stream. */
+ private OutputStream msgOut;
+
+ private SideBandOutputStream errOut;
+
+ /** Packet line input stream around {@link #rawIn}. */
+ private PacketLineIn pckIn;
+
+ /** Packet line output stream around {@link #rawOut}. */
+ private PacketLineOut pckOut;
+
+ private final MessageOutputWrapper msgOutWrapper = new MessageOutputWrapper();
+
+ private PackParser parser;
+
+ /** The refs we advertised as existing at the start of the connection. */
+ private Map<String, Ref> refs;
+
+ /** All SHA-1s shown to the client, which can be possible edges. */
+ private Set<ObjectId> advertisedHaves;
+
+ /** Capabilities requested by the client. */
+ private Set<String> enabledCapabilities;
+
+ String userAgent;
+
+ private Set<ObjectId> clientShallowCommits;
+
+ private List<ReceiveCommand> commands;
+
+ private long maxCommandBytes;
+
+ private long maxDiscardBytes;
+
+ private StringBuilder advertiseError;
+
+ /**
+ * If {@link BasePackPushConnection#CAPABILITY_SIDE_BAND_64K} is enabled.
+ */
+ private boolean sideBand;
+
+ private boolean quiet;
+
+ /** Lock around the received pack file, while updating refs. */
+ private PackLock packLock;
+
+ private boolean checkReferencedIsReachable;
+
+ /** Git object size limit */
+ private long maxObjectSizeLimit;
+
+ /** Total pack size limit */
+ private long maxPackSizeLimit = -1;
+
+ /** The size of the received pack, including index size */
+ private Long packSize;
+
+ private PushCertificateParser pushCertificateParser;
+
+ private SignedPushConfig signedPushConfig;
+
+ private PushCertificate pushCert;
+
+ private ReceivedPackStatistics stats;
+
/** Hook to validate the update commands before execution. */
private PreReceiveHook preReceive;
@@ -93,18 +312,120 @@ public class ReceivePack extends BaseReceivePack {
* the destination repository.
*/
public ReceivePack(Repository into) {
- super(into);
+ db = into;
+ walk = new RevWalk(db);
+ walk.setRetainBody(false);
+
+ TransferConfig tc = db.getConfig().get(TransferConfig.KEY);
+ objectChecker = tc.newReceiveObjectChecker();
+
+ ReceiveConfig rc = db.getConfig().get(ReceiveConfig::new);
+ allowCreates = rc.allowCreates;
+ allowAnyDeletes = true;
+ allowBranchDeletes = rc.allowDeletes;
+ allowNonFastForwards = rc.allowNonFastForwards;
+ allowOfsDelta = rc.allowOfsDelta;
+ allowPushOptions = rc.allowPushOptions;
+ maxCommandBytes = rc.maxCommandBytes;
+ maxDiscardBytes = rc.maxDiscardBytes;
+ advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
+ refFilter = RefFilter.DEFAULT;
+ advertisedHaves = new HashSet<>();
+ clientShallowCommits = new HashSet<>();
+ signedPushConfig = rc.signedPush;
preReceive = PreReceiveHook.NULL;
postReceive = PostReceiveHook.NULL;
}
+ /** Configuration for receive operations. */
+ private static class ReceiveConfig {
+ final boolean allowCreates;
+
+ final boolean allowDeletes;
+
+ final boolean allowNonFastForwards;
+
+ final boolean allowOfsDelta;
+
+ final boolean allowPushOptions;
+
+ final long maxCommandBytes;
+
+ final long maxDiscardBytes;
+
+ final SignedPushConfig signedPush;
+
+ ReceiveConfig(Config config) {
+ allowCreates = true;
+ allowDeletes = !config.getBoolean("receive", "denydeletes", false); //$NON-NLS-1$ //$NON-NLS-2$
+ allowNonFastForwards = !config.getBoolean("receive", //$NON-NLS-1$
+ "denynonfastforwards", false); //$NON-NLS-1$
+ allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", //$NON-NLS-1$ //$NON-NLS-2$
+ true);
+ allowPushOptions = config.getBoolean("receive", "pushoptions", //$NON-NLS-1$ //$NON-NLS-2$
+ false);
+ maxCommandBytes = config.getLong("receive", //$NON-NLS-1$
+ "maxCommandBytes", //$NON-NLS-1$
+ 3 << 20);
+ maxDiscardBytes = config.getLong("receive", //$NON-NLS-1$
+ "maxCommandDiscardBytes", //$NON-NLS-1$
+ -1);
+ signedPush = SignedPushConfig.KEY.parse(config);
+ }
+ }
+
+ /**
+ * Output stream that wraps the current {@link #msgOut}.
+ * <p>
+ * We don't want to expose {@link #msgOut} directly because it can change
+ * several times over the course of a session.
+ */
+ class MessageOutputWrapper extends OutputStream {
+ @Override
+ public void write(int ch) {
+ if (msgOut != null) {
+ try {
+ msgOut.write(ch);
+ } catch (IOException e) {
+ // Ignore write failures.
+ }
+ }
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) {
+ if (msgOut != null) {
+ try {
+ msgOut.write(b, off, len);
+ } catch (IOException e) {
+ // Ignore write failures.
+ }
+ }
+ }
+
+ @Override
+ public void write(byte[] b) {
+ write(b, 0, b.length);
+ }
+
+ @Override
+ public void flush() {
+ if (msgOut != null) {
+ try {
+ msgOut.flush();
+ } catch (IOException e) {
+ // Ignore write failures.
+ }
+ }
+ }
+ }
+
/**
* Get the repository this receive completes into.
*
* @return the repository this receive completes into.
*/
- @Override
- public final Repository getRepository() {
+ public Repository getRepository() {
return db;
}
@@ -113,8 +434,7 @@ public class ReceivePack extends BaseReceivePack {
*
* @return the RevWalk instance used by this connection.
*/
- @Override
- public final RevWalk getRevWalk() {
+ public RevWalk getRevWalk() {
return walk;
}
@@ -124,8 +444,7 @@ public class ReceivePack extends BaseReceivePack {
* @return all refs which were advertised to the client, or null if
* {@link #setAdvertisedRefs(Map, Set)} has not been called yet.
*/
- @Override
- public final Map<String, Ref> getAdvertisedRefs() {
+ public Map<String, Ref> getAdvertisedRefs() {
return refs;
}
@@ -146,8 +465,8 @@ public class ReceivePack extends BaseReceivePack {
* null, assumes the default set of additional haves from the
* repository.
*/
- @Override
- public void setAdvertisedRefs(Map<String, Ref> allRefs, Set<ObjectId> additionalHaves) {
+ public void setAdvertisedRefs(Map<String, Ref> allRefs,
+ Set<ObjectId> additionalHaves) {
refs = allRefs != null ? allRefs : db.getAllRefs();
refs = refFilter.filter(refs);
advertisedHaves.clear();
@@ -170,6 +489,1525 @@ public class ReceivePack extends BaseReceivePack {
}
/**
+ * Get objects advertised to the client.
+ *
+ * @return the set of objects advertised to the as present in this
+ * repository, or null if {@link #setAdvertisedRefs(Map, Set)} has
+ * not been called yet.
+ */
+ public final Set<ObjectId> getAdvertisedObjects() {
+ return advertisedHaves;
+ }
+
+ /**
+ * Whether this instance will validate all referenced, but not supplied by
+ * the client, objects are reachable from another reference.
+ *
+ * @return true if this instance will validate all referenced, but not
+ * supplied by the client, objects are reachable from another
+ * reference.
+ */
+ public boolean isCheckReferencedObjectsAreReachable() {
+ return checkReferencedIsReachable;
+ }
+
+ /**
+ * Validate all referenced but not supplied objects are reachable.
+ * <p>
+ * If enabled, this instance will verify that references to objects not
+ * contained within the received pack are already reachable through at least
+ * one other reference displayed as part of {@link #getAdvertisedRefs()}.
+ * <p>
+ * This feature is useful when the application doesn't trust the client to
+ * not provide a forged SHA-1 reference to an object, in an attempt to
+ * access parts of the DAG that they aren't allowed to see and which have
+ * been hidden from them via the configured
+ * {@link org.eclipse.jgit.transport.AdvertiseRefsHook} or
+ * {@link org.eclipse.jgit.transport.RefFilter}.
+ * <p>
+ * Enabling this feature may imply at least some, if not all, of the same
+ * functionality performed by {@link #setCheckReceivedObjects(boolean)}.
+ * Applications are encouraged to enable both features, if desired.
+ *
+ * @param b
+ * {@code true} to enable the additional check.
+ */
+ public void setCheckReferencedObjectsAreReachable(boolean b) {
+ this.checkReferencedIsReachable = b;
+ }
+
+ /**
+ * Whether this class expects a bi-directional pipe opened between the
+ * client and itself.
+ *
+ * @return true if this class expects a bi-directional pipe opened between
+ * the client and itself. The default is true.
+ */
+ public boolean isBiDirectionalPipe() {
+ return biDirectionalPipe;
+ }
+
+ /**
+ * Whether this class will assume the socket is a fully bidirectional pipe
+ * between the two peers and takes advantage of that by first transmitting
+ * the known refs, then waiting to read commands.
+ *
+ * @param twoWay
+ * if true, this class will assume the socket is a fully
+ * bidirectional pipe between the two peers and takes advantage
+ * of that by first transmitting the known refs, then waiting to
+ * read commands. If false, this class assumes it must read the
+ * commands before writing output and does not perform the
+ * initial advertising.
+ */
+ public void setBiDirectionalPipe(boolean twoWay) {
+ biDirectionalPipe = twoWay;
+ }
+
+ /**
+ * Whether there is data expected after the pack footer.
+ *
+ * @return {@code true} if there is data expected after the pack footer.
+ */
+ public boolean isExpectDataAfterPackFooter() {
+ return expectDataAfterPackFooter;
+ }
+
+ /**
+ * Whether there is additional data in InputStream after pack.
+ *
+ * @param e
+ * {@code true} if there is additional data in InputStream after
+ * pack.
+ */
+ public void setExpectDataAfterPackFooter(boolean e) {
+ expectDataAfterPackFooter = e;
+ }
+
+ /**
+ * Whether this instance will verify received objects are formatted
+ * correctly.
+ *
+ * @return {@code true} if this instance will verify received objects are
+ * formatted correctly. Validating objects requires more CPU time on
+ * this side of the connection.
+ */
+ public boolean isCheckReceivedObjects() {
+ return objectChecker != null;
+ }
+
+ /**
+ * Whether to enable checking received objects
+ *
+ * @param check
+ * {@code true} to enable checking received objects; false to
+ * assume all received objects are valid.
+ * @see #setObjectChecker(ObjectChecker)
+ */
+ public void setCheckReceivedObjects(boolean check) {
+ if (check && objectChecker == null)
+ setObjectChecker(new ObjectChecker());
+ else if (!check && objectChecker != null)
+ setObjectChecker(null);
+ }
+
+ /**
+ * Set the object checking instance to verify each received object with
+ *
+ * @param impl
+ * if non-null the object checking instance to verify each
+ * received object with; null to disable object checking.
+ * @since 3.4
+ */
+ public void setObjectChecker(ObjectChecker impl) {
+ objectChecker = impl;
+ }
+
+ /**
+ * Whether the client can request refs to be created.
+ *
+ * @return {@code true} if the client can request refs to be created.
+ */
+ public boolean isAllowCreates() {
+ return allowCreates;
+ }
+
+ /**
+ * Whether to permit create ref commands to be processed.
+ *
+ * @param canCreate
+ * {@code true} to permit create ref commands to be processed.
+ */
+ public void setAllowCreates(boolean canCreate) {
+ allowCreates = canCreate;
+ }
+
+ /**
+ * Whether the client can request refs to be deleted.
+ *
+ * @return {@code true} if the client can request refs to be deleted.
+ */
+ public boolean isAllowDeletes() {
+ return allowAnyDeletes;
+ }
+
+ /**
+ * Whether to permit delete ref commands to be processed.
+ *
+ * @param canDelete
+ * {@code true} to permit delete ref commands to be processed.
+ */
+ public void setAllowDeletes(boolean canDelete) {
+ allowAnyDeletes = canDelete;
+ }
+
+ /**
+ * Whether the client can delete from {@code refs/heads/}.
+ *
+ * @return {@code true} if the client can delete from {@code refs/heads/}.
+ * @since 3.6
+ */
+ public boolean isAllowBranchDeletes() {
+ return allowBranchDeletes;
+ }
+
+ /**
+ * Configure whether to permit deletion of branches from the
+ * {@code refs/heads/} namespace.
+ *
+ * @param canDelete
+ * {@code true} to permit deletion of branches from the
+ * {@code refs/heads/} namespace.
+ * @since 3.6
+ */
+ public void setAllowBranchDeletes(boolean canDelete) {
+ allowBranchDeletes = canDelete;
+ }
+
+ /**
+ * Whether the client can request non-fast-forward updates of a ref,
+ * possibly making objects unreachable.
+ *
+ * @return {@code true} if the client can request non-fast-forward updates
+ * of a ref, possibly making objects unreachable.
+ */
+ public boolean isAllowNonFastForwards() {
+ return allowNonFastForwards;
+ }
+
+ /**
+ * Configure whether to permit the client to ask for non-fast-forward
+ * updates of an existing ref.
+ *
+ * @param canRewind
+ * {@code true} to permit the client to ask for non-fast-forward
+ * updates of an existing ref.
+ */
+ public void setAllowNonFastForwards(boolean canRewind) {
+ allowNonFastForwards = canRewind;
+ }
+
+ /**
+ * Whether the client's commands should be performed as a single atomic
+ * transaction.
+ *
+ * @return {@code true} if the client's commands should be performed as a
+ * single atomic transaction.
+ * @since 4.4
+ */
+ public boolean isAtomic() {
+ return atomic;
+ }
+
+ /**
+ * Configure whether to perform the client's commands as a single atomic
+ * transaction.
+ *
+ * @param atomic
+ * {@code true} to perform the client's commands as a single
+ * atomic transaction.
+ * @since 4.4
+ */
+ public void setAtomic(boolean atomic) {
+ this.atomic = atomic;
+ }
+
+ /**
+ * Get identity of the user making the changes in the reflog.
+ *
+ * @return identity of the user making the changes in the reflog.
+ */
+ public PersonIdent getRefLogIdent() {
+ return refLogIdent;
+ }
+
+ /**
+ * Set the identity of the user appearing in the affected reflogs.
+ * <p>
+ * The timestamp portion of the identity is ignored. A new identity with the
+ * current timestamp will be created automatically when the updates occur
+ * and the log records are written.
+ *
+ * @param pi
+ * identity of the user. If null the identity will be
+ * automatically determined based on the repository
+ * configuration.
+ */
+ public void setRefLogIdent(PersonIdent pi) {
+ refLogIdent = pi;
+ }
+
+ /**
+ * Get the hook used while advertising the refs to the client
+ *
+ * @return the hook used while advertising the refs to the client
+ */
+ public AdvertiseRefsHook getAdvertiseRefsHook() {
+ return advertiseRefsHook;
+ }
+
+ /**
+ * Get the filter used while advertising the refs to the client
+ *
+ * @return the filter used while advertising the refs to the client
+ */
+ public RefFilter getRefFilter() {
+ return refFilter;
+ }
+
+ /**
+ * Set the hook used while advertising the refs to the client.
+ * <p>
+ * If the {@link org.eclipse.jgit.transport.AdvertiseRefsHook} chooses to
+ * call {@link #setAdvertisedRefs(Map,Set)}, only refs set by this hook
+ * <em>and</em> selected by the {@link org.eclipse.jgit.transport.RefFilter}
+ * will be shown to the client. Clients may still attempt to create or
+ * update a reference not advertised by the configured
+ * {@link org.eclipse.jgit.transport.AdvertiseRefsHook}. These attempts
+ * should be rejected by a matching
+ * {@link org.eclipse.jgit.transport.PreReceiveHook}.
+ *
+ * @param advertiseRefsHook
+ * the hook; may be null to show all refs.
+ */
+ public void setAdvertiseRefsHook(AdvertiseRefsHook advertiseRefsHook) {
+ if (advertiseRefsHook != null)
+ this.advertiseRefsHook = advertiseRefsHook;
+ else
+ this.advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
+ }
+
+ /**
+ * Set the filter used while advertising the refs to the client.
+ * <p>
+ * Only refs allowed by this filter will be shown to the client. The filter
+ * is run against the refs specified by the
+ * {@link org.eclipse.jgit.transport.AdvertiseRefsHook} (if applicable).
+ *
+ * @param refFilter
+ * the filter; may be null to show all refs.
+ */
+ public void setRefFilter(RefFilter refFilter) {
+ this.refFilter = refFilter != null ? refFilter : RefFilter.DEFAULT;
+ }
+
+ /**
+ * Get timeout (in seconds) before aborting an IO operation.
+ *
+ * @return timeout (in seconds) before aborting an IO operation.
+ */
+ public int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * Set the timeout before willing to abort an IO call.
+ *
+ * @param seconds
+ * number of seconds to wait (with no data transfer occurring)
+ * before aborting an IO read or write operation with the
+ * connected client.
+ */
+ public void setTimeout(int seconds) {
+ timeout = seconds;
+ }
+
+ /**
+ * Set the maximum number of command bytes to read from the client.
+ *
+ * @param limit
+ * command limit in bytes; if 0 there is no limit.
+ * @since 4.7
+ */
+ public void setMaxCommandBytes(long limit) {
+ maxCommandBytes = limit;
+ }
+
+ /**
+ * Set the maximum number of command bytes to discard from the client.
+ * <p>
+ * Discarding remaining bytes allows this instance to consume the rest of
+ * the command block and send a human readable over-limit error via the
+ * side-band channel. If the client sends an excessive number of bytes this
+ * limit kicks in and the instance disconnects, resulting in a non-specific
+ * 'pipe closed', 'end of stream', or similar generic error at the client.
+ * <p>
+ * When the limit is set to {@code -1} the implementation will default to
+ * the larger of {@code 3 * maxCommandBytes} or {@code 3 MiB}.
+ *
+ * @param limit
+ * discard limit in bytes; if 0 there is no limit; if -1 the
+ * implementation tries to set a reasonable default.
+ * @since 4.7
+ */
+ public void setMaxCommandDiscardBytes(long limit) {
+ maxDiscardBytes = limit;
+ }
+
+ /**
+ * Set the maximum allowed Git object size.
+ * <p>
+ * If an object is larger than the given size the pack-parsing will throw an
+ * exception aborting the receive-pack operation.
+ *
+ * @param limit
+ * the Git object size limit. If zero then there is not limit.
+ */
+ public void setMaxObjectSizeLimit(long limit) {
+ maxObjectSizeLimit = limit;
+ }
+
+ /**
+ * Set the maximum allowed pack size.
+ * <p>
+ * A pack exceeding this size will be rejected.
+ *
+ * @param limit
+ * the pack size limit, in bytes
+ * @since 3.3
+ */
+ public void setMaxPackSizeLimit(long limit) {
+ if (limit < 0)
+ throw new IllegalArgumentException(
+ MessageFormat.format(JGitText.get().receivePackInvalidLimit,
+ Long.valueOf(limit)));
+ maxPackSizeLimit = limit;
+ }
+
+ /**
+ * Check whether the client expects a side-band stream.
+ *
+ * @return true if the client has advertised a side-band capability, false
+ * otherwise.
+ * @throws org.eclipse.jgit.transport.RequestNotYetReadException
+ * if the client's request has not yet been read from the wire,
+ * so we do not know if they expect side-band. Note that the
+ * client may have already written the request, it just has not
+ * been read.
+ */
+ public boolean isSideBand() throws RequestNotYetReadException {
+ checkRequestWasRead();
+ return enabledCapabilities.contains(CAPABILITY_SIDE_BAND_64K);
+ }
+
+ /**
+ * Whether clients may request avoiding noisy progress messages.
+ *
+ * @return true if clients may request avoiding noisy progress messages.
+ * @since 4.0
+ */
+ public boolean isAllowQuiet() {
+ return allowQuiet;
+ }
+
+ /**
+ * Configure if clients may request the server skip noisy messages.
+ *
+ * @param allow
+ * true to allow clients to request quiet behavior; false to
+ * refuse quiet behavior and send messages anyway. This may be
+ * necessary if processing is slow and the client-server network
+ * connection can timeout.
+ * @since 4.0
+ */
+ public void setAllowQuiet(boolean allow) {
+ allowQuiet = allow;
+ }
+
+ /**
+ * Whether the server supports receiving push options.
+ *
+ * @return true if the server supports receiving push options.
+ * @since 4.5
+ */
+ public boolean isAllowPushOptions() {
+ return allowPushOptions;
+ }
+
+ /**
+ * Configure if the server supports receiving push options.
+ *
+ * @param allow
+ * true to optionally accept option strings from the client.
+ * @since 4.5
+ */
+ public void setAllowPushOptions(boolean allow) {
+ allowPushOptions = allow;
+ }
+
+ /**
+ * True if the client wants less verbose output.
+ *
+ * @return true if the client has requested the server to be less verbose.
+ * @throws org.eclipse.jgit.transport.RequestNotYetReadException
+ * if the client's request has not yet been read from the wire,
+ * so we do not know if they expect side-band. Note that the
+ * client may have already written the request, it just has not
+ * been read.
+ * @since 4.0
+ */
+ public boolean isQuiet() throws RequestNotYetReadException {
+ checkRequestWasRead();
+ return quiet;
+ }
+
+ /**
+ * Set the configuration for push certificate verification.
+ *
+ * @param cfg
+ * new configuration; if this object is null or its
+ * {@link SignedPushConfig#getCertNonceSeed()} is null, push
+ * certificate verification will be disabled.
+ * @since 4.1
+ */
+ public void setSignedPushConfig(SignedPushConfig cfg) {
+ signedPushConfig = cfg;
+ }
+
+ private PushCertificateParser getPushCertificateParser() {
+ if (pushCertificateParser == null) {
+ pushCertificateParser = new PushCertificateParser(db,
+ signedPushConfig);
+ }
+ return pushCertificateParser;
+ }
+
+ /**
+ * Get the user agent of the client.
+ * <p>
+ * If the client is new enough to use {@code agent=} capability that value
+ * will be returned. Older HTTP clients may also supply their version using
+ * the HTTP {@code User-Agent} header. The capability overrides the HTTP
+ * header if both are available.
+ * <p>
+ * When an HTTP request has been received this method returns the HTTP
+ * {@code User-Agent} header value until capabilities have been parsed.
+ *
+ * @return user agent supplied by the client. Available only if the client
+ * is new enough to advertise its user agent.
+ * @since 4.0
+ */
+ public String getPeerUserAgent() {
+ return UserAgent.getAgent(enabledCapabilities, userAgent);
+ }
+
+ /**
+ * Get all of the command received by the current request.
+ *
+ * @return all of the command received by the current request.
+ */
+ public List<ReceiveCommand> getAllCommands() {
+ return Collections.unmodifiableList(commands);
+ }
+
+ /**
+ * Send an error message to the client.
+ * <p>
+ * If any error messages are sent before the references are advertised to
+ * the client, the errors will be sent instead of the advertisement and the
+ * receive operation will be aborted. All clients should receive and display
+ * such early stage errors.
+ * <p>
+ * If the reference advertisements have already been sent, messages are sent
+ * in a side channel. If the client doesn't support receiving messages, the
+ * message will be discarded, with no other indication to the caller or to
+ * the client.
+ * <p>
+ * {@link org.eclipse.jgit.transport.PreReceiveHook}s should always try to
+ * use
+ * {@link org.eclipse.jgit.transport.ReceiveCommand#setResult(Result, String)}
+ * with a result status of
+ * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#REJECTED_OTHER_REASON}
+ * to indicate any reasons for rejecting an update. Messages attached to a
+ * command are much more likely to be returned to the client.
+ *
+ * @param what
+ * string describing the problem identified by the hook. The
+ * string must not end with an LF, and must not contain an LF.
+ */
+ public void sendError(String what) {
+ if (refs == null) {
+ if (advertiseError == null)
+ advertiseError = new StringBuilder();
+ advertiseError.append(what).append('\n');
+ } else {
+ msgOutWrapper.write(Constants.encode("error: " + what + "\n")); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ private void fatalError(String msg) {
+ if (errOut != null) {
+ try {
+ errOut.write(Constants.encode(msg));
+ errOut.flush();
+ } catch (IOException e) {
+ // Ignore write failures
+ }
+ } else {
+ sendError(msg);
+ }
+ }
+
+ /**
+ * Send a message to the client, if it supports receiving them.
+ * <p>
+ * If the client doesn't support receiving messages, the message will be
+ * discarded, with no other indication to the caller or to the client.
+ *
+ * @param what
+ * string describing the problem identified by the hook. The
+ * string must not end with an LF, and must not contain an LF.
+ */
+ public void sendMessage(String what) {
+ msgOutWrapper.write(Constants.encode(what + "\n")); //$NON-NLS-1$
+ }
+
+ /**
+ * Get an underlying stream for sending messages to the client.
+ *
+ * @return an underlying stream for sending messages to the client.
+ */
+ public OutputStream getMessageOutputStream() {
+ return msgOutWrapper;
+ }
+
+ /**
+ * Get whether or not a pack has been received.
+ *
+ * This can be called before calling {@link #getPackSize()} to avoid causing
+ * {@code IllegalStateException} when the pack size was not set because no
+ * pack was received.
+ *
+ * @return true if a pack has been received.
+ * @since 5.6
+ */
+ public boolean hasReceivedPack() {
+ return packSize != null;
+ }
+
+ /**
+ * Get the size of the received pack file including the index size.
+ *
+ * This can only be called if the pack is already received.
+ *
+ * @return the size of the received pack including index size
+ * @throws java.lang.IllegalStateException
+ * if called before the pack has been received
+ * @since 3.3
+ */
+ public long getPackSize() {
+ if (packSize != null)
+ return packSize.longValue();
+ throw new IllegalStateException(JGitText.get().packSizeNotSetYet);
+ }
+
+ /**
+ * Get the commits from the client's shallow file.
+ *
+ * @return if the client is a shallow repository, the list of edge commits
+ * that define the client's shallow boundary. Empty set if the
+ * client is earlier than Git 1.9, or is a full clone.
+ */
+ private Set<ObjectId> getClientShallowCommits() {
+ return clientShallowCommits;
+ }
+
+ /**
+ * Whether any commands to be executed have been read.
+ *
+ * @return {@code true} if any commands to be executed have been read.
+ */
+ private boolean hasCommands() {
+ return !commands.isEmpty();
+ }
+
+ /**
+ * Whether an error occurred that should be advertised.
+ *
+ * @return true if an error occurred that should be advertised.
+ */
+ private boolean hasError() {
+ return advertiseError != null;
+ }
+
+ /**
+ * Initialize the instance with the given streams.
+ *
+ * Visible for out-of-tree subclasses (e.g. tests that need to set the
+ * streams without going through the {@link #service()} method).
+ *
+ * @param input
+ * raw input to read client commands and pack data from. Caller
+ * must ensure the input is buffered, otherwise read performance
+ * may suffer.
+ * @param output
+ * response back to the Git network client. Caller must ensure
+ * the output is buffered, otherwise write performance may
+ * suffer.
+ * @param messages
+ * secondary "notice" channel to send additional messages out
+ * through. When run over SSH this should be tied back to the
+ * standard error channel of the command execution. For most
+ * other network connections this should be null.
+ */
+ protected void init(final InputStream input, final OutputStream output,
+ final OutputStream messages) {
+ origOut = output;
+ rawIn = input;
+ rawOut = output;
+ msgOut = messages;
+
+ if (timeout > 0) {
+ final Thread caller = Thread.currentThread();
+ timer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$
+ timeoutIn = new TimeoutInputStream(rawIn, timer);
+ TimeoutOutputStream o = new TimeoutOutputStream(rawOut, timer);
+ timeoutIn.setTimeout(timeout * 1000);
+ o.setTimeout(timeout * 1000);
+ rawIn = timeoutIn;
+ rawOut = o;
+ }
+
+ pckIn = new PacketLineIn(rawIn);
+ pckOut = new PacketLineOut(rawOut);
+ pckOut.setFlushOnEnd(false);
+
+ enabledCapabilities = new HashSet<>();
+ commands = new ArrayList<>();
+ }
+
+ /**
+ * Get advertised refs, or the default if not explicitly advertised.
+ *
+ * @return advertised refs, or the default if not explicitly advertised.
+ */
+ private Map<String, Ref> getAdvertisedOrDefaultRefs() {
+ if (refs == null)
+ setAdvertisedRefs(null, null);
+ return refs;
+ }
+
+ /**
+ * Receive a pack from the stream and check connectivity if necessary.
+ *
+ * Visible for out-of-tree subclasses. Subclasses overriding this method
+ * should invoke this implementation, as it alters the instance state (e.g.
+ * it reads the pack from the input and parses it before running the
+ * connectivity checks).
+ *
+ * @throws java.io.IOException
+ * an error occurred during unpacking or connectivity checking.
+ */
+ protected void receivePackAndCheckConnectivity() throws IOException {
+ receivePack();
+ if (needCheckConnectivity()) {
+ checkSubmodules();
+ checkConnectivity();
+ }
+ parser = null;
+ }
+
+ /**
+ * Unlock the pack written by this object.
+ *
+ * @throws java.io.IOException
+ * the pack could not be unlocked.
+ */
+ private void unlockPack() throws IOException {
+ if (packLock != null) {
+ packLock.unlock();
+ packLock = null;
+ }
+ }
+
+ /**
+ * Generate an advertisement of available refs and capabilities.
+ *
+ * @param adv
+ * the advertisement formatter.
+ * @throws java.io.IOException
+ * the formatter failed to write an advertisement.
+ * @throws org.eclipse.jgit.transport.ServiceMayNotContinueException
+ * the hook denied advertisement.
+ */
+ public void sendAdvertisedRefs(RefAdvertiser adv)
+ throws IOException, ServiceMayNotContinueException {
+ if (advertiseError != null) {
+ adv.writeOne("ERR " + advertiseError); //$NON-NLS-1$
+ return;
+ }
+
+ try {
+ advertiseRefsHook.advertiseRefs(this);
+ } catch (ServiceMayNotContinueException fail) {
+ if (fail.getMessage() != null) {
+ adv.writeOne("ERR " + fail.getMessage()); //$NON-NLS-1$
+ fail.setOutput();
+ }
+ throw fail;
+ }
+
+ adv.init(db);
+ adv.advertiseCapability(CAPABILITY_SIDE_BAND_64K);
+ adv.advertiseCapability(CAPABILITY_DELETE_REFS);
+ adv.advertiseCapability(CAPABILITY_REPORT_STATUS);
+ if (allowQuiet)
+ adv.advertiseCapability(CAPABILITY_QUIET);
+ String nonce = getPushCertificateParser().getAdvertiseNonce();
+ if (nonce != null) {
+ adv.advertiseCapability(nonce);
+ }
+ if (db.getRefDatabase().performsAtomicTransactions())
+ adv.advertiseCapability(CAPABILITY_ATOMIC);
+ if (allowOfsDelta)
+ adv.advertiseCapability(CAPABILITY_OFS_DELTA);
+ if (allowPushOptions) {
+ adv.advertiseCapability(CAPABILITY_PUSH_OPTIONS);
+ }
+ adv.advertiseCapability(OPTION_AGENT, UserAgent.get());
+ adv.send(getAdvertisedOrDefaultRefs().values());
+ for (ObjectId obj : advertisedHaves)
+ adv.advertiseHave(obj);
+ if (adv.isEmpty())
+ adv.advertiseId(ObjectId.zeroId(), "capabilities^{}"); //$NON-NLS-1$
+ adv.end();
+ }
+
+ /**
+ * Returns the statistics on the received pack if available. This should be
+ * called after {@link #receivePack} is called.
+ *
+ * @return ReceivedPackStatistics
+ * @since 4.6
+ */
+ @Nullable
+ public ReceivedPackStatistics getReceivedPackStatistics() {
+ return stats;
+ }
+
+ /**
+ * Receive a list of commands from the input.
+ *
+ * @throws java.io.IOException
+ */
+ private void recvCommands() throws IOException {
+ PacketLineIn pck = maxCommandBytes > 0
+ ? new PacketLineIn(rawIn, maxCommandBytes)
+ : pckIn;
+ PushCertificateParser certParser = getPushCertificateParser();
+ boolean firstPkt = true;
+ try {
+ for (;;) {
+ String line;
+ try {
+ line = pck.readString();
+ } catch (EOFException eof) {
+ if (commands.isEmpty())
+ return;
+ throw eof;
+ }
+ if (PacketLineIn.isEnd(line)) {
+ break;
+ }
+
+ if (line.length() >= 48 && line.startsWith("shallow ")) { //$NON-NLS-1$
+ parseShallow(line.substring(8, 48));
+ continue;
+ }
+
+ if (firstPkt) {
+ firstPkt = false;
+ FirstCommand firstLine = FirstCommand.fromLine(line);
+ enabledCapabilities = firstLine.getCapabilities();
+ line = firstLine.getLine();
+ enableCapabilities();
+
+ if (line.equals(GitProtocolConstants.OPTION_PUSH_CERT)) {
+ certParser.receiveHeader(pck, !isBiDirectionalPipe());
+ continue;
+ }
+ }
+
+ if (line.equals(PushCertificateParser.BEGIN_SIGNATURE)) {
+ certParser.receiveSignature(pck);
+ continue;
+ }
+
+ ReceiveCommand cmd = parseCommand(line);
+ if (cmd.getRefName().equals(Constants.HEAD)) {
+ cmd.setResult(Result.REJECTED_CURRENT_BRANCH);
+ } else {
+ cmd.setRef(refs.get(cmd.getRefName()));
+ }
+ commands.add(cmd);
+ if (certParser.enabled()) {
+ certParser.addCommand(cmd);
+ }
+ }
+ pushCert = certParser.build();
+ if (hasCommands()) {
+ readPostCommands(pck);
+ }
+ } catch (PackProtocolException e) {
+ discardCommands();
+ fatalError(e.getMessage());
+ throw e;
+ } catch (InputOverLimitIOException e) {
+ String msg = JGitText.get().tooManyCommands;
+ discardCommands();
+ fatalError(msg);
+ throw new PackProtocolException(msg);
+ }
+ }
+
+ private void discardCommands() {
+ if (sideBand) {
+ long max = maxDiscardBytes;
+ if (max < 0) {
+ max = Math.max(3 * maxCommandBytes, 3L << 20);
+ }
+ try {
+ new PacketLineIn(rawIn, max).discardUntilEnd();
+ } catch (IOException e) {
+ // Ignore read failures attempting to discard.
+ }
+ }
+ }
+
+ private void parseShallow(String idStr) throws PackProtocolException {
+ ObjectId id;
+ try {
+ id = ObjectId.fromString(idStr);
+ } catch (InvalidObjectIdException e) {
+ throw new PackProtocolException(e.getMessage(), e);
+ }
+ clientShallowCommits.add(id);
+ }
+
+ /**
+ * @param in
+ * request stream.
+ * @throws IOException
+ * request line cannot be read.
+ */
+ void readPostCommands(PacketLineIn in) throws IOException {
+ if (usePushOptions) {
+ pushOptions = new ArrayList<>(4);
+ for (;;) {
+ String option = in.readString();
+ if (PacketLineIn.isEnd(option)) {
+ break;
+ }
+ pushOptions.add(option);
+ }
+ }
+ }
+
+ /**
+ * Enable capabilities based on a previously read capabilities line.
+ */
+ private void enableCapabilities() {
+ reportStatus = isCapabilityEnabled(CAPABILITY_REPORT_STATUS);
+ usePushOptions = isCapabilityEnabled(CAPABILITY_PUSH_OPTIONS);
+ sideBand = isCapabilityEnabled(CAPABILITY_SIDE_BAND_64K);
+ quiet = allowQuiet && isCapabilityEnabled(CAPABILITY_QUIET);
+ if (sideBand) {
+ OutputStream out = rawOut;
+
+ rawOut = new SideBandOutputStream(CH_DATA, MAX_BUF, out);
+ msgOut = new SideBandOutputStream(CH_PROGRESS, MAX_BUF, out);
+ errOut = new SideBandOutputStream(CH_ERROR, MAX_BUF, out);
+
+ pckOut = new PacketLineOut(rawOut);
+ pckOut.setFlushOnEnd(false);
+ }
+ }
+
+ /**
+ * Check if the peer requested a capability.
+ *
+ * @param name
+ * protocol name identifying the capability.
+ * @return true if the peer requested the capability to be enabled.
+ */
+ private boolean isCapabilityEnabled(String name) {
+ return enabledCapabilities.contains(name);
+ }
+
+ private void checkRequestWasRead() {
+ if (enabledCapabilities == null)
+ throw new RequestNotYetReadException();
+ }
+
+ /**
+ * Whether a pack is expected based on the list of commands.
+ *
+ * @return {@code true} if a pack is expected based on the list of commands.
+ */
+ private boolean needPack() {
+ for (ReceiveCommand cmd : commands) {
+ if (cmd.getType() != ReceiveCommand.Type.DELETE)
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Receive a pack from the input and store it in the repository.
+ *
+ * @throws IOException
+ * an error occurred reading or indexing the pack.
+ */
+ private void receivePack() throws IOException {
+ // It might take the client a while to pack the objects it needs
+ // to send to us. We should increase our timeout so we don't
+ // abort while the client is computing.
+ //
+ if (timeoutIn != null)
+ timeoutIn.setTimeout(10 * timeout * 1000);
+
+ ProgressMonitor receiving = NullProgressMonitor.INSTANCE;
+ ProgressMonitor resolving = NullProgressMonitor.INSTANCE;
+ if (sideBand && !quiet)
+ resolving = new SideBandProgressMonitor(msgOut);
+
+ try (ObjectInserter ins = db.newObjectInserter()) {
+ String lockMsg = "jgit receive-pack"; //$NON-NLS-1$
+ if (getRefLogIdent() != null)
+ lockMsg += " from " + getRefLogIdent().toExternalString(); //$NON-NLS-1$
+
+ parser = ins.newPackParser(packInputStream());
+ parser.setAllowThin(true);
+ parser.setNeedNewObjectIds(checkReferencedIsReachable);
+ parser.setNeedBaseObjectIds(checkReferencedIsReachable);
+ parser.setCheckEofAfterPackFooter(
+ !biDirectionalPipe && !isExpectDataAfterPackFooter());
+ parser.setExpectDataAfterPackFooter(isExpectDataAfterPackFooter());
+ parser.setObjectChecker(objectChecker);
+ parser.setLockMessage(lockMsg);
+ parser.setMaxObjectSizeLimit(maxObjectSizeLimit);
+ packLock = parser.parse(receiving, resolving);
+ packSize = Long.valueOf(parser.getPackSize());
+ stats = parser.getReceivedPackStatistics();
+ ins.flush();
+ }
+
+ if (timeoutIn != null)
+ timeoutIn.setTimeout(timeout * 1000);
+ }
+
+ private InputStream packInputStream() {
+ InputStream packIn = rawIn;
+ if (maxPackSizeLimit >= 0) {
+ packIn = new LimitedInputStream(packIn, maxPackSizeLimit) {
+ @Override
+ protected void limitExceeded() throws TooLargePackException {
+ throw new TooLargePackException(limit);
+ }
+ };
+ }
+ return packIn;
+ }
+
+ private boolean needCheckConnectivity() {
+ return isCheckReceivedObjects()
+ || isCheckReferencedObjectsAreReachable()
+ || !getClientShallowCommits().isEmpty();
+ }
+
+ private void checkSubmodules() throws IOException {
+ ObjectDatabase odb = db.getObjectDatabase();
+ if (objectChecker == null) {
+ return;
+ }
+ for (GitmoduleEntry entry : objectChecker.getGitsubmodules()) {
+ AnyObjectId blobId = entry.getBlobId();
+ ObjectLoader blob = odb.open(blobId, Constants.OBJ_BLOB);
+
+ try {
+ SubmoduleValidator.assertValidGitModulesFile(
+ new String(blob.getBytes(), UTF_8));
+ } catch (LargeObjectException | SubmoduleValidationException e) {
+ throw new IOException(e);
+ }
+ }
+ }
+
+ private void checkConnectivity() throws IOException {
+ ObjectIdSubclassMap<ObjectId> baseObjects = null;
+ ObjectIdSubclassMap<ObjectId> providedObjects = null;
+ ProgressMonitor checking = NullProgressMonitor.INSTANCE;
+ if (sideBand && !quiet) {
+ SideBandProgressMonitor m = new SideBandProgressMonitor(msgOut);
+ m.setDelayStart(750, TimeUnit.MILLISECONDS);
+ checking = m;
+ }
+
+ if (checkReferencedIsReachable) {
+ baseObjects = parser.getBaseObjectIds();
+ providedObjects = parser.getNewObjectIds();
+ }
+ parser = null;
+
+ try (ObjectWalk ow = new ObjectWalk(db)) {
+ if (baseObjects != null) {
+ ow.sort(RevSort.TOPO);
+ if (!baseObjects.isEmpty())
+ ow.sort(RevSort.BOUNDARY, true);
+ }
+
+ for (ReceiveCommand cmd : commands) {
+ if (cmd.getResult() != Result.NOT_ATTEMPTED)
+ continue;
+ if (cmd.getType() == ReceiveCommand.Type.DELETE)
+ continue;
+ ow.markStart(ow.parseAny(cmd.getNewId()));
+ }
+ for (ObjectId have : advertisedHaves) {
+ RevObject o = ow.parseAny(have);
+ ow.markUninteresting(o);
+
+ if (baseObjects != null && !baseObjects.isEmpty()) {
+ o = ow.peel(o);
+ if (o instanceof RevCommit)
+ o = ((RevCommit) o).getTree();
+ if (o instanceof RevTree)
+ ow.markUninteresting(o);
+ }
+ }
+
+ checking.beginTask(JGitText.get().countingObjects,
+ ProgressMonitor.UNKNOWN);
+ RevCommit c;
+ while ((c = ow.next()) != null) {
+ checking.update(1);
+ if (providedObjects != null //
+ && !c.has(RevFlag.UNINTERESTING) //
+ && !providedObjects.contains(c))
+ throw new MissingObjectException(c, Constants.TYPE_COMMIT);
+ }
+
+ RevObject o;
+ while ((o = ow.nextObject()) != null) {
+ checking.update(1);
+ if (o.has(RevFlag.UNINTERESTING))
+ continue;
+
+ if (providedObjects != null) {
+ if (providedObjects.contains(o)) {
+ continue;
+ }
+ throw new MissingObjectException(o, o.getType());
+ }
+
+ if (o instanceof RevBlob && !db.getObjectDatabase().has(o))
+ throw new MissingObjectException(o, Constants.TYPE_BLOB);
+ }
+ checking.endTask();
+
+ if (baseObjects != null) {
+ for (ObjectId id : baseObjects) {
+ o = ow.parseAny(id);
+ if (!o.has(RevFlag.UNINTERESTING))
+ throw new MissingObjectException(o, o.getType());
+ }
+ }
+ }
+ }
+
+ /**
+ * Validate the command list.
+ */
+ private void validateCommands() {
+ for (ReceiveCommand cmd : commands) {
+ final Ref ref = cmd.getRef();
+ if (cmd.getResult() != Result.NOT_ATTEMPTED)
+ continue;
+
+ if (cmd.getType() == ReceiveCommand.Type.DELETE) {
+ if (!isAllowDeletes()) {
+ // Deletes are not supported on this repository.
+ cmd.setResult(Result.REJECTED_NODELETE);
+ continue;
+ }
+ if (!isAllowBranchDeletes()
+ && ref.getName().startsWith(Constants.R_HEADS)) {
+ // Branches cannot be deleted, but other refs can.
+ cmd.setResult(Result.REJECTED_NODELETE);
+ continue;
+ }
+ }
+
+ if (cmd.getType() == ReceiveCommand.Type.CREATE) {
+ if (!isAllowCreates()) {
+ cmd.setResult(Result.REJECTED_NOCREATE);
+ continue;
+ }
+
+ if (ref != null && !isAllowNonFastForwards()) {
+ // Creation over an existing ref is certainly not going
+ // to be a fast-forward update. We can reject it early.
+ //
+ cmd.setResult(Result.REJECTED_NONFASTFORWARD);
+ continue;
+ }
+
+ if (ref != null) {
+ // A well behaved client shouldn't have sent us a
+ // create command for a ref we advertised to it.
+ //
+ cmd.setResult(Result.REJECTED_OTHER_REASON,
+ JGitText.get().refAlreadyExists);
+ continue;
+ }
+ }
+
+ if (cmd.getType() == ReceiveCommand.Type.DELETE && ref != null) {
+ ObjectId id = ref.getObjectId();
+ if (id == null) {
+ id = ObjectId.zeroId();
+ }
+ if (!ObjectId.zeroId().equals(cmd.getOldId())
+ && !id.equals(cmd.getOldId())) {
+ // Delete commands can be sent with the old id matching our
+ // advertised value, *OR* with the old id being 0{40}. Any
+ // other requested old id is invalid.
+ //
+ cmd.setResult(Result.REJECTED_OTHER_REASON,
+ JGitText.get().invalidOldIdSent);
+ continue;
+ }
+ }
+
+ if (cmd.getType() == ReceiveCommand.Type.UPDATE) {
+ if (ref == null) {
+ // The ref must have been advertised in order to be updated.
+ //
+ cmd.setResult(Result.REJECTED_OTHER_REASON,
+ JGitText.get().noSuchRef);
+ continue;
+ }
+ ObjectId id = ref.getObjectId();
+ if (id == null) {
+ // We cannot update unborn branch
+ cmd.setResult(Result.REJECTED_OTHER_REASON,
+ JGitText.get().cannotUpdateUnbornBranch);
+ continue;
+ }
+
+ if (!id.equals(cmd.getOldId())) {
+ // A properly functioning client will send the same
+ // object id we advertised.
+ //
+ cmd.setResult(Result.REJECTED_OTHER_REASON,
+ JGitText.get().invalidOldIdSent);
+ continue;
+ }
+
+ // Is this possibly a non-fast-forward style update?
+ //
+ RevObject oldObj, newObj;
+ try {
+ oldObj = walk.parseAny(cmd.getOldId());
+ } catch (IOException e) {
+ cmd.setResult(Result.REJECTED_MISSING_OBJECT,
+ cmd.getOldId().name());
+ continue;
+ }
+
+ try {
+ newObj = walk.parseAny(cmd.getNewId());
+ } catch (IOException e) {
+ cmd.setResult(Result.REJECTED_MISSING_OBJECT,
+ cmd.getNewId().name());
+ continue;
+ }
+
+ if (oldObj instanceof RevCommit
+ && newObj instanceof RevCommit) {
+ try {
+ if (walk.isMergedInto((RevCommit) oldObj,
+ (RevCommit) newObj))
+ cmd.setTypeFastForwardUpdate();
+ else
+ cmd.setType(
+ ReceiveCommand.Type.UPDATE_NONFASTFORWARD);
+ } catch (MissingObjectException e) {
+ cmd.setResult(Result.REJECTED_MISSING_OBJECT,
+ e.getMessage());
+ } catch (IOException e) {
+ cmd.setResult(Result.REJECTED_OTHER_REASON);
+ }
+ } else {
+ cmd.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD);
+ }
+
+ if (cmd.getType() == ReceiveCommand.Type.UPDATE_NONFASTFORWARD
+ && !isAllowNonFastForwards()) {
+ cmd.setResult(Result.REJECTED_NONFASTFORWARD);
+ continue;
+ }
+ }
+
+ if (!cmd.getRefName().startsWith(Constants.R_REFS)
+ || !Repository.isValidRefName(cmd.getRefName())) {
+ cmd.setResult(Result.REJECTED_OTHER_REASON,
+ JGitText.get().funnyRefname);
+ }
+ }
+ }
+
+ /**
+ * Whether any commands have been rejected so far.
+ *
+ * @return if any commands have been rejected so far.
+ */
+ private boolean anyRejects() {
+ for (ReceiveCommand cmd : commands) {
+ if (cmd.getResult() != Result.NOT_ATTEMPTED
+ && cmd.getResult() != Result.OK)
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Set the result to fail for any command that was not processed yet.
+ *
+ */
+ private void failPendingCommands() {
+ ReceiveCommand.abort(commands);
+ }
+
+ /**
+ * Filter the list of commands according to result.
+ *
+ * @param want
+ * desired status to filter by.
+ * @return a copy of the command list containing only those commands with
+ * the desired status.
+ */
+ private List<ReceiveCommand> filterCommands(Result want) {
+ return ReceiveCommand.filter(commands, want);
+ }
+
+ /**
+ * Execute commands to update references.
+ */
+ private void executeCommands() {
+ List<ReceiveCommand> toApply = filterCommands(Result.NOT_ATTEMPTED);
+ if (toApply.isEmpty())
+ return;
+
+ ProgressMonitor updating = NullProgressMonitor.INSTANCE;
+ if (sideBand) {
+ SideBandProgressMonitor pm = new SideBandProgressMonitor(msgOut);
+ pm.setDelayStart(250, TimeUnit.MILLISECONDS);
+ updating = pm;
+ }
+
+ BatchRefUpdate batch = db.getRefDatabase().newBatchUpdate();
+ batch.setAllowNonFastForwards(isAllowNonFastForwards());
+ batch.setAtomic(isAtomic());
+ batch.setRefLogIdent(getRefLogIdent());
+ batch.setRefLogMessage("push", true); //$NON-NLS-1$
+ batch.addCommand(toApply);
+ try {
+ batch.setPushCertificate(getPushCertificate());
+ batch.execute(walk, updating);
+ } catch (IOException err) {
+ for (ReceiveCommand cmd : toApply) {
+ if (cmd.getResult() == Result.NOT_ATTEMPTED)
+ cmd.reject(err);
+ }
+ }
+ }
+
+ /**
+ * Send a status report.
+ *
+ * @param forClient
+ * true if this report is for a Git client, false if it is for an
+ * end-user.
+ * @param unpackError
+ * an error that occurred during unpacking, or {@code null}
+ * @param out
+ * the reporter for sending the status strings.
+ * @throws java.io.IOException
+ * an error occurred writing the status report.
+ * @since 5.6
+ */
+ private void sendStatusReport(final boolean forClient,
+ final Throwable unpackError, final Reporter out)
+ throws IOException {
+ if (unpackError != null) {
+ out.sendString("unpack error " + unpackError.getMessage()); //$NON-NLS-1$
+ if (forClient) {
+ for (ReceiveCommand cmd : commands) {
+ out.sendString("ng " + cmd.getRefName() //$NON-NLS-1$
+ + " n/a (unpacker error)"); //$NON-NLS-1$
+ }
+ }
+ return;
+ }
+
+ if (forClient)
+ out.sendString("unpack ok"); //$NON-NLS-1$
+ for (ReceiveCommand cmd : commands) {
+ if (cmd.getResult() == Result.OK) {
+ if (forClient)
+ out.sendString("ok " + cmd.getRefName()); //$NON-NLS-1$
+ continue;
+ }
+
+ final StringBuilder r = new StringBuilder();
+ if (forClient)
+ r.append("ng ").append(cmd.getRefName()).append(" "); //$NON-NLS-1$ //$NON-NLS-2$
+ else
+ r.append(" ! [rejected] ").append(cmd.getRefName()) //$NON-NLS-1$
+ .append(" ("); //$NON-NLS-1$
+
+ switch (cmd.getResult()) {
+ case NOT_ATTEMPTED:
+ r.append("server bug; ref not processed"); //$NON-NLS-1$
+ break;
+
+ case REJECTED_NOCREATE:
+ r.append("creation prohibited"); //$NON-NLS-1$
+ break;
+
+ case REJECTED_NODELETE:
+ r.append("deletion prohibited"); //$NON-NLS-1$
+ break;
+
+ case REJECTED_NONFASTFORWARD:
+ r.append("non-fast forward"); //$NON-NLS-1$
+ break;
+
+ case REJECTED_CURRENT_BRANCH:
+ r.append("branch is currently checked out"); //$NON-NLS-1$
+ break;
+
+ case REJECTED_MISSING_OBJECT:
+ if (cmd.getMessage() == null)
+ r.append("missing object(s)"); //$NON-NLS-1$
+ else if (cmd.getMessage()
+ .length() == Constants.OBJECT_ID_STRING_LENGTH) {
+ r.append("object "); //$NON-NLS-1$
+ r.append(cmd.getMessage());
+ r.append(" missing"); //$NON-NLS-1$
+ } else
+ r.append(cmd.getMessage());
+ break;
+
+ case REJECTED_OTHER_REASON:
+ if (cmd.getMessage() == null)
+ r.append("unspecified reason"); //$NON-NLS-1$
+ else
+ r.append(cmd.getMessage());
+ break;
+
+ case LOCK_FAILURE:
+ r.append("failed to lock"); //$NON-NLS-1$
+ break;
+
+ case OK:
+ // We shouldn't have reached this case (see 'ok' case above).
+ continue;
+ }
+ if (!forClient)
+ r.append(")"); //$NON-NLS-1$
+ out.sendString(r.toString());
+ }
+ }
+
+ /**
+ * Close and flush (if necessary) the underlying streams.
+ *
+ * @throws java.io.IOException
+ */
+ private void close() throws IOException {
+ if (sideBand) {
+ // If we are using side band, we need to send a final
+ // flush-pkt to tell the remote peer the side band is
+ // complete and it should stop decoding. We need to
+ // use the original output stream as rawOut is now the
+ // side band data channel.
+ //
+ ((SideBandOutputStream) msgOut).flushBuffer();
+ ((SideBandOutputStream) rawOut).flushBuffer();
+
+ PacketLineOut plo = new PacketLineOut(origOut);
+ plo.setFlushOnEnd(false);
+ plo.end();
+ }
+
+ if (biDirectionalPipe) {
+ // If this was a native git connection, flush the pipe for
+ // the caller. For smart HTTP we don't do this flush and
+ // instead let the higher level HTTP servlet code do it.
+ //
+ if (!sideBand && msgOut != null)
+ msgOut.flush();
+ rawOut.flush();
+ }
+ }
+
+ /**
+ * Release any resources used by this object.
+ *
+ * @throws java.io.IOException
+ * the pack could not be unlocked.
+ */
+ private void release() throws IOException {
+ walk.close();
+ unlockPack();
+ timeoutIn = null;
+ rawIn = null;
+ rawOut = null;
+ msgOut = null;
+ pckIn = null;
+ pckOut = null;
+ refs = null;
+ // Keep the capabilities. If responses are sent after this release
+ // we need to remember at least whether sideband communication has to be
+ // used
+ commands = null;
+ if (timer != null) {
+ try {
+ timer.terminate();
+ } finally {
+ timer = null;
+ }
+ }
+ }
+
+ /** Interface for reporting status messages. */
+ abstract static class Reporter {
+ abstract void sendString(String s) throws IOException;
+ }
+
+ /**
* Get the push certificate used to verify the pusher's identity.
* <p>
* Only valid after commands are read from the wire.
@@ -178,7 +2016,6 @@ public class ReceivePack extends BaseReceivePack {
* or no cert was presented by the client.
* @since 4.1
*/
- @Override
public PushCertificate getPushCertificate() {
return pushCert;
}
@@ -193,7 +2030,6 @@ public class ReceivePack extends BaseReceivePack {
* the push certificate to set.
* @since 4.1
*/
- @Override
public void setPushCertificate(PushCertificate cert) {
pushCert = cert;
}
@@ -334,28 +2170,6 @@ public class ReceivePack extends BaseReceivePack {
}
}
- /** {@inheritDoc} */
- @Override
- protected void enableCapabilities() {
- reportStatus = isCapabilityEnabled(CAPABILITY_REPORT_STATUS);
- usePushOptions = isCapabilityEnabled(CAPABILITY_PUSH_OPTIONS);
- super.enableCapabilities();
- }
-
- @Override
- void readPostCommands(PacketLineIn in) throws IOException {
- if (usePushOptions) {
- pushOptions = new ArrayList<>(4);
- for (;;) {
- String option = in.readString();
- if (PacketLineIn.isEnd(option)) {
- break;
- }
- pushOptions.add(option);
- }
- }
- }
-
private void service() throws IOException {
if (isBiDirectionalPipe()) {
sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
@@ -377,8 +2191,7 @@ public class ReceivePack extends BaseReceivePack {
try {
if (unpackError == null) {
- boolean atomic = isCapabilityEnabled(CAPABILITY_ATOMIC);
- setAtomic(atomic);
+ setAtomic(isCapabilityEnabled(CAPABILITY_ATOMIC));
validateCommands();
if (atomic && anyRejects()) {
@@ -437,9 +2250,27 @@ public class ReceivePack extends BaseReceivePack {
repo.autoGC(NullProgressMonitor.INSTANCE);
}
- /** {@inheritDoc} */
- @Override
- protected String getLockMessageProcessName() {
- return "jgit receive-pack"; //$NON-NLS-1$
+ static ReceiveCommand parseCommand(String line)
+ throws PackProtocolException {
+ if (line == null || line.length() < 83) {
+ throw new PackProtocolException(
+ JGitText.get().errorInvalidProtocolWantedOldNewRef);
+ }
+ String oldStr = line.substring(0, 40);
+ String newStr = line.substring(41, 81);
+ ObjectId oldId, newId;
+ try {
+ oldId = ObjectId.fromString(oldStr);
+ newId = ObjectId.fromString(newStr);
+ } catch (InvalidObjectIdException e) {
+ throw new PackProtocolException(
+ JGitText.get().errorInvalidProtocolWantedOldNewRef, e);
+ }
+ String name = line.substring(82);
+ if (!Repository.isValidRefName(name)) {
+ throw new PackProtocolException(
+ JGitText.get().errorInvalidProtocolWantedOldNewRef);
+ }
+ return new ReceiveCommand(oldId, newId, name);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java
index 0a621f19e1..4474d1a858 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteConfig.java
@@ -292,22 +292,26 @@ public class RemoteConfig implements Serializable {
private String replaceUri(final String uri,
final Map<String, String> replacements) {
- if (replacements.isEmpty())
+ if (replacements.isEmpty()) {
return uri;
+ }
Entry<String, String> match = null;
for (Entry<String, String> replacement : replacements.entrySet()) {
// Ignore current entry if not longer than previous match
if (match != null
- && match.getKey().length() > replacement.getKey().length())
+ && match.getKey().length() > replacement.getKey()
+ .length()) {
continue;
- if (!uri.startsWith(replacement.getKey()))
+ }
+ if (!uri.startsWith(replacement.getKey())) {
continue;
+ }
match = replacement;
}
- if (match != null)
+ if (match != null) {
return match.getValue() + uri.substring(match.getKey().length());
- else
- return uri;
+ }
+ return uri;
}
/**
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 c34e3b8e61..faa2c76988 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
@@ -72,7 +72,7 @@ public class RemoteRefUpdate {
/**
* Represent current status of a remote ref update.
*/
- public static enum Status {
+ public enum Status {
/**
* Push process hasn't yet attempted to update this ref. This is the
* default status, prior to push process execution.
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 758d74c3fa..100b433e00 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
@@ -132,6 +132,7 @@ public class TransferConfig {
private final boolean allowReachableSha1InWant;
private final boolean allowFilter;
private final boolean allowSidebandAll;
+ private final boolean advertiseSidebandAll;
final @Nullable ProtocolVersion protocolVersion;
final String[] hideRefs;
@@ -213,6 +214,8 @@ public class TransferConfig {
hideRefs = rc.getStringList("uploadpack", null, "hiderefs");
allowSidebandAll = rc.getBoolean(
"uploadpack", "allowsidebandall", false);
+ advertiseSidebandAll = rc.getBoolean("uploadpack",
+ "advertisesidebandall", false);
}
/**
@@ -295,7 +298,8 @@ public class TransferConfig {
}
/**
- * @return true if clients are allowed to specify a "sideband-all" line
+ * @return true if the server accepts sideband-all requests (see
+ * {{@link #isAdvertiseSidebandAll()} for the advertisement)
* @since 5.5
*/
public boolean isAllowSidebandAll() {
@@ -303,6 +307,14 @@ public class TransferConfig {
}
/**
+ * @return true to advertise sideband all to the clients
+ * @since 5.6
+ */
+ public boolean isAdvertiseSidebandAll() {
+ return advertiseSidebandAll && allowSidebandAll;
+ }
+
+ /**
* Get {@link org.eclipse.jgit.transport.RefFilter} respecting configured
* hidden refs.
*
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 0b7907035e..2382f4c3af 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -302,11 +302,12 @@ public abstract class Transport implements AutoCloseable {
URISyntaxException, TransportException {
if (local != null) {
final RemoteConfig cfg = new RemoteConfig(local.getConfig(), remote);
- if (doesNotExist(cfg))
+ if (doesNotExist(cfg)) {
return open(local, new URIish(remote), null);
+ }
return open(local, cfg, op);
- } else
- return open(new URIish(remote));
+ }
+ return open(new URIish(remote));
}
@@ -708,11 +709,11 @@ public abstract class Transport implements AutoCloseable {
// try to find matching tracking refs
for (RefSpec fetchSpec : fetchSpecs) {
if (fetchSpec.matchSource(remoteName)) {
- if (fetchSpec.isWildcard())
+ if (fetchSpec.isWildcard()) {
return fetchSpec.expandFromSource(remoteName)
.getDestination();
- else
- return fetchSpec.getDestination();
+ }
+ return fetchSpec.getDestination();
}
}
return null;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
index 3029327c52..2637a0273a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
@@ -698,9 +698,8 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
public CredentialItem[] items() {
if (forRepo == null) {
return new CredentialItem[] { message, now, always };
- } else {
- return new CredentialItem[] { message, now, forRepo, always };
}
+ return new CredentialItem[] { message, now, forRepo, always };
}
}
@@ -1076,13 +1075,11 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
host = host.toLowerCase(Locale.ROOT);
if (host.equals(cookieDomain)) {
return true;
- } else {
- if (!host.endsWith(cookieDomain)) {
- return false;
- }
- return host
- .charAt(host.length() - cookieDomain.length() - 1) == '.';
}
+ if (!host.endsWith(cookieDomain)) {
+ return false;
+ }
+ return host.charAt(host.length() - cookieDomain.length() - 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 de58aa9e1b..5a8cd32a90 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportProtocol.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportProtocol.java
@@ -90,7 +90,7 @@ import org.eclipse.jgit.lib.Repository;
*/
public abstract class TransportProtocol {
/** Fields within a {@link URIish} that a transport uses. */
- public static enum URIishField {
+ public enum URIishField {
/** the user field */
USER,
/** the pass (aka password) field */
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 7ca9cc134c..d5e03ebd57 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
@@ -351,10 +351,7 @@ public class URIish implements Serializable {
}
private String n2e(String s) {
- if (s == null)
- return ""; //$NON-NLS-1$
- else
- return s;
+ return s == null ? "" : s; //$NON-NLS-1$
}
// takes care to cut of a leading slash if a windows drive letter or a
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 20b45882df..8a88e175fe 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -44,6 +44,7 @@
package org.eclipse.jgit.transport;
import static java.util.Collections.unmodifiableMap;
+import static java.util.Objects.requireNonNull;
import static org.eclipse.jgit.lib.Constants.R_TAGS;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REF_IN_WANT;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SERVER_OPTION;
@@ -84,7 +85,9 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
@@ -134,7 +137,7 @@ import org.eclipse.jgit.util.io.TimeoutOutputStream;
*/
public class UploadPack {
/** Policy the server uses to validate client requests */
- public static enum RequestPolicy {
+ public enum RequestPolicy {
/** Client may only ask for objects the server advertised a reference for. */
ADVERTISED,
@@ -282,6 +285,8 @@ public class UploadPack {
private OutputStream msgOut = NullOutputStream.INSTANCE;
+ private ErrorWriter errOut = new PackProtocolErrorWriter();
+
/**
* Refs eligible for advertising to the client, set using
* {@link #setAdvertisedRefs}.
@@ -756,9 +761,59 @@ public class UploadPack {
/**
* Execute the upload task on the socket.
*
- * <p>If the client passed extra parameters (e.g., "version=2") through a
- * side channel, the caller must call setExtraParameters first to supply
- * them.
+ * <p>
+ * Same as {@link #uploadWithExceptionPropagation} except that the thrown
+ * exceptions are handled in the method, and the error messages are sent to
+ * the clients.
+ *
+ * <p>
+ * Call this method if the caller does not have an error handling mechanism.
+ * Call {@link #uploadWithExceptionPropagation} if the caller wants to have
+ * its own error handling mechanism.
+ *
+ * @param input
+ * @param output
+ * @param messages
+ * @throws java.io.IOException
+ */
+ public void upload(InputStream input, OutputStream output,
+ @Nullable OutputStream messages) throws IOException {
+ try {
+ uploadWithExceptionPropagation(input, output, messages);
+ } catch (ServiceMayNotContinueException err) {
+ if (!err.isOutput() && err.getMessage() != null) {
+ try {
+ errOut.writeError(err.getMessage());
+ } catch (IOException e) {
+ err.addSuppressed(e);
+ throw err;
+ }
+ err.setOutput();
+ }
+ throw err;
+ } catch (IOException | RuntimeException | Error err) {
+ if (rawOut != null) {
+ String msg = err instanceof PackProtocolException
+ ? err.getMessage()
+ : JGitText.get().internalServerError;
+ try {
+ errOut.writeError(msg);
+ } catch (IOException e) {
+ err.addSuppressed(e);
+ throw err;
+ }
+ throw new UploadPackInternalServerErrorException(err);
+ }
+ throw err;
+ }
+ }
+
+ /**
+ * Execute the upload task on the socket.
+ *
+ * <p>
+ * If the client passed extra parameters (e.g., "version=2") through a side
+ * channel, the caller must call setExtraParameters first to supply them.
*
* @param input
* raw input to read client commands from. Caller must ensure the
@@ -772,15 +827,21 @@ public class UploadPack {
* through. When run over SSH this should be tied back to the
* standard error channel of the command execution. For most
* other network connections this should be null.
- * @throws java.io.IOException
+ * @throws ServiceMayNotContinueException
+ * thrown if one of the hooks throws this.
+ * @throws IOException
+ * thrown if the server or the client I/O fails, or there's an
+ * internal server error.
+ * @since 5.6
*/
- public void upload(InputStream input, OutputStream output,
- @Nullable OutputStream messages) throws IOException {
- PacketLineOut pckOut = null;
+ public void uploadWithExceptionPropagation(InputStream input,
+ OutputStream output, @Nullable OutputStream messages)
+ throws ServiceMayNotContinueException, IOException {
try {
rawIn = input;
- if (messages != null)
+ if (messages != null) {
msgOut = messages;
+ }
if (timeout > 0) {
final Thread caller = Thread.currentThread();
@@ -800,42 +861,12 @@ public class UploadPack {
}
pckIn = new PacketLineIn(rawIn);
- pckOut = new PacketLineOut(rawOut);
+ PacketLineOut pckOut = new PacketLineOut(rawOut);
if (useProtocolV2()) {
serviceV2(pckOut);
} else {
service(pckOut);
}
- } catch (UploadPackInternalServerErrorException err) {
- // UploadPackInternalServerErrorException is a special exception
- // that indicates an error is already written to the client. Do
- // nothing.
- throw err;
- } catch (ServiceMayNotContinueException err) {
- if (!err.isOutput() && err.getMessage() != null && pckOut != null) {
- try {
- pckOut.writeString("ERR " + err.getMessage() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
- } catch (IOException e) {
- err.addSuppressed(e);
- throw err;
- }
- err.setOutput();
- }
- throw err;
- } catch (IOException | RuntimeException | Error err) {
- if (pckOut != null) {
- String msg = err instanceof PackProtocolException
- ? err.getMessage()
- : JGitText.get().internalServerError;
- try {
- pckOut.writeString("ERR " + msg + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
- } catch (IOException e) {
- err.addSuppressed(e);
- throw err;
- }
- throw new UploadPackInternalServerErrorException(err);
- }
- throw err;
} finally {
msgOut = NullOutputStream.INSTANCE;
walk.close();
@@ -1296,7 +1327,7 @@ public class UploadPack {
caps.add(COMMAND_FETCH + '='
+ (transferConfig.isAllowFilter() ? OPTION_FILTER + ' ' : "")
+ (advertiseRefInWant ? CAPABILITY_REF_IN_WANT + ' ' : "")
- + (transferConfig.isAllowSidebandAll()
+ + (transferConfig.isAdvertiseSidebandAll()
? OPTION_SIDEBAND_ALL + ' '
: "")
+ (cachedPackUriProvider != null ? "packfile-uris " : "")
@@ -1844,8 +1875,7 @@ public class UploadPack {
@Override
public void checkWants(UploadPack up, List<ObjectId> wants)
throws PackProtocolException, IOException {
- checkNotAdvertisedWants(up, wants,
- refIdSet(up.getAdvertisedRefs().values()));
+ checkNotAdvertisedWants(up, wants, up.getAdvertisedRefs().values());
}
}
@@ -1882,7 +1912,7 @@ public class UploadPack {
public void checkWants(UploadPack up, List<ObjectId> wants)
throws PackProtocolException, IOException {
checkNotAdvertisedWants(up, wants,
- refIdSet(up.getRepository().getRefDatabase().getRefs()));
+ up.getRepository().getRefDatabase().getRefs());
}
}
@@ -1942,12 +1972,14 @@ public class UploadPack {
}
private static void checkNotAdvertisedWants(UploadPack up,
- List<ObjectId> notAdvertisedWants, Set<ObjectId> reachableFrom)
+ List<ObjectId> notAdvertisedWants, Collection<Ref> visibleRefs)
throws IOException {
ObjectReader reader = up.getRevWalk().getObjectReader();
+
try (RevWalk walk = new RevWalk(reader)) {
walk.setRetainBody(false);
+ Set<ObjectId> reachableFrom = refIdSet(visibleRefs);
// Missing "wants" throw exception here
List<RevObject> wantsAsObjs = objectIdsToRevObjects(walk,
notAdvertisedWants);
@@ -1994,10 +2026,13 @@ public class UploadPack {
ReachabilityChecker reachabilityChecker = walk
.createReachabilityChecker();
- List<RevCommit> starters = objectIdsToRevCommits(walk,
- reachableFrom);
+ Stream<RevCommit> reachableCommits = importantRefsFirst(visibleRefs)
+ .map(UploadPack::refToObjectId)
+ .map(objId -> objectIdToRevCommit(walk, objId))
+ .filter(Objects::nonNull); // Ignore missing tips
+
Optional<RevCommit> unreachable = reachabilityChecker
- .areAllReachable(wantsAsCommits, starters);
+ .areAllReachable(wantsAsCommits, reachableCommits);
if (unreachable.isPresent()) {
throw new WantNotValidException(unreachable.get());
}
@@ -2007,6 +2042,50 @@ public class UploadPack {
}
}
+ static Stream<Ref> importantRefsFirst(
+ Collection<Ref> visibleRefs) {
+ Predicate<Ref> startsWithRefsHeads = ref -> ref.getName()
+ .startsWith(Constants.R_HEADS);
+ Predicate<Ref> startsWithRefsTags = ref -> ref.getName()
+ .startsWith(Constants.R_TAGS);
+ Predicate<Ref> allOther = ref -> !startsWithRefsHeads.test(ref)
+ && !startsWithRefsTags.test(ref);
+
+ return Stream.concat(
+ visibleRefs.stream().filter(startsWithRefsHeads),
+ Stream.concat(
+ visibleRefs.stream().filter(startsWithRefsTags),
+ visibleRefs.stream().filter(allOther)));
+ }
+
+ private static ObjectId refToObjectId(Ref ref) {
+ return ref.getObjectId() != null ? ref.getObjectId()
+ : ref.getPeeledObjectId();
+ }
+
+ /**
+ * Translate an object id to a RevCommit.
+ *
+ * @param walk
+ * walk on the relevant object storae
+ * @param objectId
+ * Object Id
+ * @return RevCommit instance or null if the object is missing
+ */
+ @Nullable
+ private static RevCommit objectIdToRevCommit(RevWalk walk,
+ ObjectId objectId) {
+ if (objectId == null) {
+ return null;
+ }
+
+ try {
+ return walk.parseCommit(objectId);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
// Resolve the ObjectIds into RevObjects. Any missing object raises an
// exception
private static List<RevObject> objectIdsToRevObjects(RevWalk walk,
@@ -2019,23 +2098,6 @@ public class UploadPack {
return result;
}
- // Get commits from object ids. If the id is not a commit, ignore it. If the
- // id doesn't exist, report the missing object in a exception.
- private static List<RevCommit> objectIdsToRevCommits(RevWalk walk,
- Iterable<ObjectId> objectIds)
- throws MissingObjectException, IOException {
- List<RevCommit> result = new ArrayList<>();
- for (ObjectId objectId : objectIds) {
- try {
- result.add(walk.parseCommit(objectId));
- } catch (IncorrectObjectTypeException e) {
- continue;
- }
- }
- return result;
- }
-
-
private void addCommonBase(RevObject o) {
if (!o.has(COMMON)) {
o.add(COMMON);
@@ -2114,54 +2176,42 @@ public class UploadPack {
Set<String> caps = req.getClientCapabilities();
boolean sideband = caps.contains(OPTION_SIDE_BAND)
|| caps.contains(OPTION_SIDE_BAND_64K);
+
if (sideband) {
- try {
- sendPack(true, req, accumulator, allTags, unshallowCommits,
- deepenNots, pckOut);
- } catch (ServiceMayNotContinueException err) {
- String message = err.getMessage();
- if (message == null) {
- message = JGitText.get().internalServerError;
- }
- try {
- reportInternalServerErrorOverSideband(message);
- } catch (IOException e) {
- err.addSuppressed(e);
- throw err;
- }
- throw new UploadPackInternalServerErrorException(err);
- } catch (IOException | RuntimeException | Error err) {
- try {
- reportInternalServerErrorOverSideband(
- JGitText.get().internalServerError);
- } catch (IOException e) {
- err.addSuppressed(e);
- throw err;
- }
- throw new UploadPackInternalServerErrorException(err);
+ errOut = new SideBandErrorWriter();
+
+ int bufsz = SideBandOutputStream.SMALL_BUF;
+ if (req.getClientCapabilities().contains(OPTION_SIDE_BAND_64K)) {
+ bufsz = SideBandOutputStream.MAX_BUF;
}
+ OutputStream packOut = new SideBandOutputStream(
+ SideBandOutputStream.CH_DATA, bufsz, rawOut);
+
+ ProgressMonitor pm = NullProgressMonitor.INSTANCE;
+ if (!req.getClientCapabilities().contains(OPTION_NO_PROGRESS)) {
+ msgOut = new SideBandOutputStream(
+ SideBandOutputStream.CH_PROGRESS, bufsz, rawOut);
+ pm = new SideBandProgressMonitor(msgOut);
+ }
+
+ sendPack(pm, pckOut, packOut, req, accumulator, allTags,
+ unshallowCommits, deepenNots);
+ pckOut.end();
} else {
- sendPack(false, req, accumulator, allTags, unshallowCommits, deepenNots,
- pckOut);
+ sendPack(NullProgressMonitor.INSTANCE, pckOut, rawOut, req,
+ accumulator, allTags, unshallowCommits, deepenNots);
}
}
- private void reportInternalServerErrorOverSideband(String message)
- throws IOException {
- @SuppressWarnings("resource" /* java 7 */)
- SideBandOutputStream err = new SideBandOutputStream(
- SideBandOutputStream.CH_ERROR, SideBandOutputStream.SMALL_BUF,
- rawOut);
- err.write(Constants.encode(message));
- err.flush();
- }
-
/**
* Send the requested objects to the client.
*
- * @param sideband
- * whether to wrap the pack in side-band pkt-lines, interleaved
- * with progress messages and errors.
+ * @param pm
+ * progress monitor
+ * @param pckOut
+ * PacketLineOut that shares the output with packOut
+ * @param packOut
+ * packfile output
* @param req
* request being processed
* @param accumulator
@@ -2173,35 +2223,14 @@ public class UploadPack {
* shallow commits on the client that are now becoming unshallow
* @param deepenNots
* objects that the client specified using --shallow-exclude
- * @param pckOut
- * output writer
* @throws IOException
* if an error occurred while generating or writing the pack.
*/
- private void sendPack(final boolean sideband,
- FetchRequest req,
+ private void sendPack(ProgressMonitor pm, PacketLineOut pckOut,
+ OutputStream packOut, FetchRequest req,
PackStatistics.Accumulator accumulator,
- @Nullable Collection<Ref> allTags,
- List<ObjectId> unshallowCommits,
- List<ObjectId> deepenNots,
- PacketLineOut pckOut) throws IOException {
- ProgressMonitor pm = NullProgressMonitor.INSTANCE;
- OutputStream packOut = rawOut;
-
- if (sideband) {
- int bufsz = SideBandOutputStream.SMALL_BUF;
- if (req.getClientCapabilities().contains(OPTION_SIDE_BAND_64K))
- bufsz = SideBandOutputStream.MAX_BUF;
-
- packOut = new SideBandOutputStream(SideBandOutputStream.CH_DATA,
- bufsz, rawOut);
- if (!req.getClientCapabilities().contains(OPTION_NO_PROGRESS)) {
- msgOut = new SideBandOutputStream(
- SideBandOutputStream.CH_PROGRESS, bufsz, rawOut);
- pm = new SideBandProgressMonitor(msgOut);
- }
- }
-
+ @Nullable Collection<Ref> allTags, List<ObjectId> unshallowCommits,
+ List<ObjectId> deepenNots) throws IOException {
if (wantAll.isEmpty()) {
preUploadHook.onSendPack(this, wantIds, commonBase);
} else {
@@ -2344,9 +2373,6 @@ public class UploadPack {
}
pw.close();
}
-
- if (sideband)
- pckOut.end();
}
private static void findSymrefs(
@@ -2385,12 +2411,12 @@ public class UploadPack {
}
@Override
- public void write(byte b[]) throws IOException {
+ public void write(byte[] b) throws IOException {
out.write(b);
}
@Override
- public void write(byte b[], int off, int len) throws IOException {
+ public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
}
@@ -2411,4 +2437,28 @@ public class UploadPack {
}
}
}
+
+ private interface ErrorWriter {
+ void writeError(String message) throws IOException;
+ }
+
+ private class SideBandErrorWriter implements ErrorWriter {
+ @Override
+ public void writeError(String message) throws IOException {
+ @SuppressWarnings("resource" /* java 7 */)
+ SideBandOutputStream err = new SideBandOutputStream(
+ SideBandOutputStream.CH_ERROR,
+ SideBandOutputStream.SMALL_BUF, requireNonNull(rawOut));
+ err.write(Constants.encode(message));
+ err.flush();
+ }
+ }
+
+ private class PackProtocolErrorWriter implements ErrorWriter {
+ @Override
+ public void writeError(String message) throws IOException {
+ new PacketLineOut(requireNonNull(rawOut))
+ .writeString("ERR " + message + '\n'); //$NON-NLS-1$
+ }
+ }
}
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 7a973aff34..fb8a55f1b5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkEncryption.java
@@ -324,7 +324,7 @@ abstract class WalkEncryption {
* Base implementation of JGit symmetric encryption. Supports V2 properties
* format.
*/
- static abstract class SymmetricEncryption extends WalkEncryption
+ abstract static class SymmetricEncryption extends WalkEncryption
implements Keys, Vals {
/** Encryption profile, root name of group of related properties. */
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 b4a7af094d..9a08c080c3 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
@@ -124,8 +124,8 @@ public class FileResolver<C> implements RepositoryResolver<C> {
// are responsible for closing the repository if we
// complete successfully.
return db;
- } else
- throw new ServiceNotEnabledException();
+ }
+ throw new ServiceNotEnabledException();
} catch (RuntimeException | IOException e) {
db.close();
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 4f3eb05b11..5f5da58771 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java
@@ -275,7 +275,7 @@ public class FileTreeIterator extends WorkingTreeIterator {
/**
* a singleton instance of the default FileModeStrategy
*/
- public final static DefaultFileModeStrategy INSTANCE =
+ public static final DefaultFileModeStrategy INSTANCE =
new DefaultFileModeStrategy();
@Override
@@ -285,9 +285,8 @@ public class FileTreeIterator extends WorkingTreeIterator {
} else if (attributes.isDirectory()) {
if (new File(f, Constants.DOT_GIT).exists()) {
return FileMode.GITLINK;
- } else {
- return FileMode.TREE;
}
+ return FileMode.TREE;
} else if (attributes.isExecutable()) {
return FileMode.EXECUTABLE_FILE;
} else {
@@ -309,7 +308,7 @@ public class FileTreeIterator extends WorkingTreeIterator {
/**
* a singleton instance of the default FileModeStrategy
*/
- public final static NoGitlinksStrategy INSTANCE = new NoGitlinksStrategy();
+ public static final NoGitlinksStrategy INSTANCE = new NoGitlinksStrategy();
@Override
public FileMode getMode(File f, FS.Attributes attributes) {
@@ -425,9 +424,8 @@ public class FileTreeIterator extends WorkingTreeIterator {
if (attributes.isSymbolicLink()) {
return new ByteArrayInputStream(fs.readSymLink(getFile())
.getBytes(UTF_8));
- } else {
- return new FileInputStream(getFile());
}
+ return new FileInputStream(getFile());
}
/**
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 65d8512179..e545565e8f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -111,7 +111,7 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
/**
* @since 4.2
*/
- public static enum OperationType {
+ public enum OperationType {
/**
* Represents a checkout operation (for example a checkout or reset
* operation).
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 8bb68dca17..35d6e41a9e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -101,7 +101,6 @@ import org.eclipse.jgit.util.Paths;
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.TemporaryBuffer;
import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;
-import org.eclipse.jgit.util.io.AutoLFInputStream;
import org.eclipse.jgit.util.io.EolStreamTypeUtil;
import org.eclipse.jgit.util.sha1.SHA1;
@@ -686,10 +685,10 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
public InputStream openEntryStream() throws IOException {
InputStream rawis = current().openInputStream();
if (getCleanFilterCommand() == null
- && getEolStreamType() == EolStreamType.DIRECT)
+ && getEolStreamType() == EolStreamType.DIRECT) {
return rawis;
- else
- return filterClean(rawis);
+ }
+ return filterClean(rawis);
}
/**
@@ -980,13 +979,13 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
MetadataDiff diff = compareMetadata(entry);
switch (diff) {
case DIFFER_BY_TIMESTAMP:
- if (forceContentCheck)
+ if (forceContentCheck) {
// But we are told to look at content even though timestamps
// tell us about modification
return contentCheck(entry, reader);
- else
- // We are told to assume a modification if timestamps differs
- return true;
+ }
+ // We are told to assume a modification if timestamps differs
+ return true;
case SMUDGED:
// The file is clean by timestamps but the entry was smudged.
// Lets do a content check
@@ -1087,47 +1086,13 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
entry.setLength((int) getEntryLength());
return false;
- } else {
- if (mode == FileMode.SYMLINK.getBits()) {
- return !new File(readSymlinkTarget(current())).equals(
- new File(readContentAsNormalizedString(entry, reader)));
- }
- // Content differs: that's a real change, perhaps
- if (reader == null) // deprecated use, do no further checks
- return true;
-
- switch (getEolStreamType()) {
- case DIRECT:
- return true;
- default:
- try {
- ObjectLoader loader = reader.open(entry.getObjectId());
- if (loader == null)
- return true;
-
- // We need to compute the length, but only if it is not
- // a binary stream.
- long dcInLen;
- try (InputStream dcIn = new AutoLFInputStream(
- loader.openStream(), true,
- true /* abort if binary */)) {
- dcInLen = computeLength(dcIn);
- } catch (AutoLFInputStream.IsBinaryException e) {
- return true;
- }
-
- try (InputStream dcIn = new AutoLFInputStream(
- loader.openStream(), true)) {
- byte[] autoCrLfHash = computeHash(dcIn, dcInLen);
- boolean changed = getEntryObjectId()
- .compareTo(autoCrLfHash, 0) != 0;
- return changed;
- }
- } catch (IOException e) {
- return true;
- }
- }
}
+ if (mode == FileMode.SYMLINK.getBits()) {
+ return !new File(readSymlinkTarget(current())).equals(
+ new File(readContentAsNormalizedString(entry, reader)));
+ }
+ // Content differs: that's a real change
+ return true;
}
private static String readContentAsNormalizedString(DirCacheEntry entry,
@@ -1215,7 +1180,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
*
* @since 5.0
*/
- public static abstract class Entry {
+ public abstract static class Entry {
byte[] encodedName;
int encodedNameLen;
@@ -1535,7 +1500,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
}
// Read blob from index and check for CR/LF-delimited text.
DirCacheEntry entry = dirCache.getDirCacheEntry();
- if (FileMode.REGULAR_FILE.equals(entry.getFileMode())) {
+ if ((entry.getRawMode() & FileMode.TYPE_MASK) == FileMode.TYPE_FILE) {
ObjectId blobId = entry.getObjectId();
if (entry.getStage() > 0
&& entry.getStage() != DirCacheEntry.STAGE_2) {
@@ -1552,7 +1517,10 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
break;
}
if (entry.getStage() == DirCacheEntry.STAGE_2) {
- blobId = entry.getObjectId();
+ if ((entry.getRawMode()
+ & FileMode.TYPE_MASK) == FileMode.TYPE_FILE) {
+ blobId = entry.getObjectId();
+ }
break;
}
}
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 6cca582b3b..52fb888291 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
@@ -212,10 +212,9 @@ public class IndexDiffFilter extends TreeFilter {
// If i is cnt then the path does not appear in any other tree,
// and this working tree entry can be safely ignored.
return i != cnt;
- } else {
- // In working tree and not ignored, and not in DirCache.
- return true;
}
+ // In working tree and not ignored, and not in DirCache.
+ return true;
}
// Always include subtrees as WorkingTreeIterator cannot provide
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 3d9f875e99..11896e4242 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
@@ -99,10 +99,10 @@ public class PathSuffixFilter extends TreeFilter {
@Override
public boolean include(TreeWalk walker) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
- if (walker.isSubtree())
+ if (walker.isSubtree()) {
return true;
- else
- return walker.isPathSuffix(pathRaw, pathRaw.length);
+ }
+ return walker.isPathSuffix(pathRaw, pathRaw.length);
}
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 69f8547452..119c96e02e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Base64.java
@@ -28,26 +28,26 @@ import org.eclipse.jgit.internal.JGitText;
*/
public class Base64 {
/** The equals sign (=) as a byte. */
- private final static byte EQUALS_SIGN = (byte) '=';
+ private static final byte EQUALS_SIGN = (byte) '=';
/** Indicates equals sign in encoding. */
- private final static byte EQUALS_SIGN_DEC = -1;
+ private static final byte EQUALS_SIGN_DEC = -1;
/** Indicates white space in encoding. */
- private final static byte WHITE_SPACE_DEC = -2;
+ private static final byte WHITE_SPACE_DEC = -2;
/** Indicates an invalid byte during decoding. */
- private final static byte INVALID_DEC = -3;
+ private static final byte INVALID_DEC = -3;
/** The 64 valid Base64 values. */
- private final static byte[] ENC;
+ private static final byte[] ENC;
/**
* Translates a Base64 value to either its 6-bit reconstruction value or a
* negative number indicating some other meaning. The table is only 7 bits
* wide, as the 8th bit is discarded during decoding.
*/
- private final static byte[] DEC;
+ private static final byte[] DEC;
static {
ENC = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" // //$NON-NLS-1$
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 c8e6645f57..957cb5af6d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
@@ -173,20 +173,19 @@ public class ChangeIdUtil {
boolean replaceExisting) {
int indexOfChangeId = indexOfChangeId(message, "\n"); //$NON-NLS-1$
if (indexOfChangeId > 0) {
- if (!replaceExisting)
+ if (!replaceExisting) {
return message;
- else {
- StringBuilder ret = new StringBuilder(message.substring(0,
- indexOfChangeId));
- ret.append(CHANGE_ID);
- ret.append(" I"); //$NON-NLS-1$
- ret.append(ObjectId.toString(changeId));
- int indexOfNextLineBreak = message.indexOf("\n", //$NON-NLS-1$
- indexOfChangeId);
- if (indexOfNextLineBreak > 0)
- ret.append(message.substring(indexOfNextLineBreak));
- return ret.toString();
}
+ StringBuilder ret = new StringBuilder(
+ message.substring(0, indexOfChangeId));
+ ret.append(CHANGE_ID);
+ ret.append(" I"); //$NON-NLS-1$
+ ret.append(ObjectId.toString(changeId));
+ int indexOfNextLineBreak = message.indexOf('\n',
+ indexOfChangeId);
+ if (indexOfNextLineBreak > 0)
+ ret.append(message.substring(indexOfNextLineBreak));
+ return ret.toString();
}
String[] lines = message.split("\n"); //$NON-NLS-1$
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 0aa0e3668d..41bfde9914 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java
@@ -61,6 +61,7 @@ import java.nio.charset.Charset;
import java.nio.file.AccessDeniedException;
import java.nio.file.FileStore;
import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
@@ -98,6 +99,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.lib.Config;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
@@ -147,15 +149,15 @@ public abstract class FS {
*/
public FS detect(Boolean cygwinUsed) {
if (SystemReader.getInstance().isWindows()) {
- if (cygwinUsed == null)
+ if (cygwinUsed == null) {
cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin());
- if (cygwinUsed.booleanValue())
+ }
+ if (cygwinUsed.booleanValue()) {
return new FS_Win32_Cygwin();
- else
- return new FS_Win32();
- } else {
- return new FS_POSIX();
+ }
+ return new FS_Win32();
}
+ return new FS_POSIX();
}
}
@@ -211,7 +213,7 @@ public abstract class FS {
*
* @since 5.1.9
*/
- public final static class FileStoreAttributes {
+ public static final class FileStoreAttributes {
private static final Duration UNDEFINED_DURATION = Duration
.ofNanos(Long.MAX_VALUE);
@@ -240,13 +242,21 @@ public abstract class FS {
private static Map<FileStore, Lock> locks = new ConcurrentHashMap<>();
- private static void setBackground(boolean async) {
+ /**
+ * Whether FileStore attributes should be determined asynchronously
+ *
+ * @param async
+ * 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.6.2
+ */
+ public static void setBackground(boolean async) {
background.set(async);
}
- private static final String javaVersionPrefix = SystemReader
- .getInstance().getHostname() + '|'
- + System.getProperty("java.vendor") + '|' //$NON-NLS-1$
+ private static final String javaVersionPrefix = System
+ .getProperty("java.vendor") + '|' //$NON-NLS-1$
+ System.getProperty("java.version") + '|'; //$NON-NLS-1$
private static final Duration FALLBACK_MIN_RACY_INTERVAL = Duration
@@ -689,7 +699,7 @@ public abstract class FS {
/** The auto-detected implementation selected for this operating system and JRE. */
public static final FS DETECTED = detect();
- private volatile static FSFactory factory;
+ private static volatile FSFactory factory;
/**
* Auto-detect the appropriate file system abstraction.
@@ -708,7 +718,9 @@ public abstract class FS {
* 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);
}
@@ -837,7 +849,7 @@ public abstract class FS {
try {
FileUtils.delete(tempFile);
} catch (IOException e) {
- throw new RuntimeException(e); // panic
+ LOG.error(JGitText.get().cannotDeleteFile, tempFile);
}
}
}
@@ -1192,14 +1204,13 @@ public abstract class FS {
gobbler.join();
if (rc == 0 && !gobbler.fail.get()) {
return r;
- } else {
- if (debug) {
- LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
- }
- throw new CommandFailedException(rc,
- gobbler.errorMessage.get(),
- gobbler.exception.get());
}
+ if (debug) {
+ LOG.debug("readpipe rc=" + rc); //$NON-NLS-1$
+ }
+ throw new CommandFailedException(rc,
+ gobbler.errorMessage.get(),
+ gobbler.exception.get());
} catch (InterruptedException ie) {
// Stop bothering me, I have a zombie to reap.
}
@@ -1726,20 +1737,18 @@ public abstract class FS {
final String hookName, String[] args, PrintStream outRedirect,
PrintStream errRedirect, String stdinArgs)
throws JGitInternalException {
- final File hookFile = findHook(repository, hookName);
- if (hookFile == null)
+ File hookFile = findHook(repository, hookName);
+ if (hookFile == null || hookName == null) {
return new ProcessResult(Status.NOT_PRESENT);
+ }
- final String hookPath = hookFile.getAbsolutePath();
- final File runDirectory;
- if (repository.isBare())
- runDirectory = repository.getDirectory();
- else
- runDirectory = repository.getWorkTree();
- final String cmd = relativize(runDirectory.getAbsolutePath(),
- hookPath);
- ProcessBuilder hookProcess = runInShell(cmd, args);
- hookProcess.directory(runDirectory);
+ File runDirectory = getRunDirectory(repository, hookName);
+ if (runDirectory == null) {
+ return new ProcessResult(Status.NOT_PRESENT);
+ }
+ String cmd = hookFile.getAbsolutePath();
+ ProcessBuilder hookProcess = runInShell(shellQuote(cmd), args);
+ hookProcess.directory(runDirectory.getAbsoluteFile());
Map<String, String> environment = hookProcess.environment();
environment.put(Constants.GIT_DIR_KEY,
repository.getDirectory().getAbsolutePath());
@@ -1761,6 +1770,21 @@ public abstract class FS {
}
}
+ /**
+ * Quote a string (such as a file system path obtained from a Java
+ * {@link File} or {@link Path} object) such that it can be passed as first
+ * argument to {@link #runInShell(String, String[])}.
+ * <p>
+ * This default implementation returns the string unchanged.
+ * </p>
+ *
+ * @param cmd
+ * the String to quote
+ * @return the quoted string
+ */
+ String shellQuote(String cmd) {
+ return cmd;
+ }
/**
* Tries to find a hook matching the given one in the given repository.
@@ -1774,12 +1798,71 @@ public abstract class FS {
* @since 4.0
*/
public File findHook(Repository repository, String hookName) {
- File gitDir = repository.getDirectory();
- if (gitDir == null)
+ if (hookName == null) {
+ return null;
+ }
+ File hookDir = getHooksDirectory(repository);
+ if (hookDir == null) {
return null;
- final File hookFile = new File(new File(gitDir,
- Constants.HOOKS), hookName);
- return hookFile.isFile() ? hookFile : null;
+ }
+ File hookFile = new File(hookDir, hookName);
+ if (hookFile.isAbsolute()) {
+ if (!hookFile.exists() || (FS.DETECTED.supportsExecute()
+ && !FS.DETECTED.canExecute(hookFile))) {
+ return null;
+ }
+ } else {
+ try {
+ File runDirectory = getRunDirectory(repository, hookName);
+ if (runDirectory == null) {
+ return null;
+ }
+ Path hookPath = runDirectory.getAbsoluteFile().toPath()
+ .resolve(hookFile.toPath());
+ FS fs = repository.getFS();
+ if (fs == null) {
+ fs = FS.DETECTED;
+ }
+ if (!Files.exists(hookPath) || (fs.supportsExecute()
+ && !fs.canExecute(hookPath.toFile()))) {
+ return null;
+ }
+ hookFile = hookPath.toFile();
+ } catch (InvalidPathException e) {
+ LOG.warn(MessageFormat.format(JGitText.get().invalidHooksPath,
+ hookFile));
+ return null;
+ }
+ }
+ return hookFile;
+ }
+
+ private File getRunDirectory(Repository repository,
+ @NonNull String hookName) {
+ if (repository.isBare()) {
+ return repository.getDirectory();
+ }
+ switch (hookName) {
+ case "pre-receive": //$NON-NLS-1$
+ case "update": //$NON-NLS-1$
+ case "post-receive": //$NON-NLS-1$
+ case "post-update": //$NON-NLS-1$
+ case "push-to-checkout": //$NON-NLS-1$
+ return repository.getDirectory();
+ default:
+ return repository.getWorkTree();
+ }
+ }
+
+ private File getHooksDirectory(Repository repository) {
+ Config config = repository.getConfig();
+ String hooksDir = config.getString(ConfigConstants.CONFIG_CORE_SECTION,
+ null, ConfigConstants.CONFIG_KEY_HOOKS_PATH);
+ if (hooksDir != null) {
+ return new File(hooksDir);
+ }
+ File dir = repository.getDirectory();
+ return dir == null ? null : new File(dir, Constants.HOOKS);
}
/**
@@ -2213,7 +2296,7 @@ public abstract class FS {
void copy() throws IOException {
boolean writeFailure = false;
- byte buffer[] = new byte[4096];
+ byte[] buffer = new byte[4096];
int readBytes;
while ((readBytes = in.read(buffer)) != -1) {
// Do not try to write again after a failure, but keep
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 6a1eef2d66..9c8dab68cd 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
@@ -74,7 +74,6 @@ import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.CommandFailedException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.slf4j.Logger;
@@ -86,7 +85,7 @@ import org.slf4j.LoggerFactory;
* @since 3.0
*/
public class FS_POSIX extends FS {
- private final static Logger LOG = LoggerFactory.getLogger(FS_POSIX.class);
+ private static final Logger LOG = LoggerFactory.getLogger(FS_POSIX.class);
private static final int DEFAULT_UMASK = 0022;
private volatile int umask = -1;
@@ -270,6 +269,11 @@ public class FS_POSIX extends FS {
return proc;
}
+ @Override
+ String shellQuote(String cmd) {
+ return QuotedString.BOURNE.quote(cmd);
+ }
+
/** {@inheritDoc} */
@Override
public ProcessResult runHookIfPresent(Repository repository, String hookName,
@@ -311,20 +315,6 @@ public class FS_POSIX extends FS {
/** {@inheritDoc} */
@Override
- public File findHook(Repository repository, String hookName) {
- final File gitdir = repository.getDirectory();
- if (gitdir == null) {
- return null;
- }
- final Path hookPath = gitdir.toPath().resolve(Constants.HOOKS)
- .resolve(hookName);
- if (Files.isExecutable(hookPath))
- return hookPath.toFile();
- return null;
- }
-
- /** {@inheritDoc} */
- @Override
public boolean supportsAtomicCreateNewFile() {
if (supportsAtomicFileCreation == AtomicFileCreation.UNDEFINED) {
try {
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 1e64a38bb1..aedf43c9e1 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
@@ -72,7 +72,7 @@ import org.slf4j.LoggerFactory;
* @since 3.0
*/
public class FS_Win32 extends FS {
- private final static Logger LOG = LoggerFactory.getLogger(FS_Win32.class);
+ private static final Logger LOG = LoggerFactory.getLogger(FS_Win32.class);
/**
* Constructor
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 9a163e8e38..ac788a6684 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
@@ -47,8 +47,6 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.File;
import java.io.PrintStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
@@ -57,7 +55,6 @@ import java.util.List;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.CommandFailedException;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -68,7 +65,7 @@ import org.slf4j.LoggerFactory;
* @since 3.0
*/
public class FS_Win32_Cygwin extends FS_Win32 {
- private final static Logger LOG = LoggerFactory
+ private static final Logger LOG = LoggerFactory
.getLogger(FS_Win32_Cygwin.class);
private static String cygpath;
@@ -160,6 +157,11 @@ public class FS_Win32_Cygwin extends FS_Win32 {
return proc;
}
+ @Override
+ String shellQuote(String cmd) {
+ return QuotedString.BOURNE.quote(cmd.replace(File.separatorChar, '/'));
+ }
+
/** {@inheritDoc} */
@Override
public String relativize(String base, String other) {
@@ -175,18 +177,4 @@ public class FS_Win32_Cygwin extends FS_Win32 {
return internalRunHookIfPresent(repository, hookName, args, outRedirect,
errRedirect, stdinArgs);
}
-
- /** {@inheritDoc} */
- @Override
- public File findHook(Repository repository, String hookName) {
- final File gitdir = repository.getDirectory();
- if (gitdir == null) {
- return null;
- }
- final Path hookPath = gitdir.toPath().resolve(Constants.HOOKS)
- .resolve(hookName);
- if (Files.isExecutable(hookPath))
- return hookPath.toFile();
- return null;
- }
}
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 4d791e470a..e026e9274f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
@@ -73,6 +73,7 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+import java.util.Random;
import java.util.regex.Pattern;
import org.eclipse.jgit.internal.JGitText;
@@ -87,6 +88,8 @@ import org.slf4j.LoggerFactory;
public class FileUtils {
private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);
+ private static final Random RNG = new Random();
+
/**
* Option to delete given {@code File}
*/
@@ -986,4 +989,28 @@ public class FileUtils {
}
Files.setLastModifiedTime(f, FileTime.from(Instant.now()));
}
+
+ /**
+ * Compute a delay in a {@code min..max} interval with random jitter.
+ *
+ * @param last
+ * amount of delay waited before the last attempt. This is used
+ * to seed the next delay interval. Should be 0 if there was no
+ * prior delay.
+ * @param min
+ * shortest amount of allowable delay between attempts.
+ * @param max
+ * longest amount of allowable delay between attempts.
+ * @return new amount of delay to wait before the next attempt.
+ *
+ * @since 5.6
+ */
+ public static long delay(long last, long min, long max) {
+ long r = Math.max(0, last * 3 - min);
+ if (r > 0) {
+ int c = (int) Math.min(r + 1, Integer.MAX_VALUE);
+ r = RNG.nextInt(c);
+ }
+ return Math.max(Math.min(min + r, max), min);
+ }
}
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 e461902a31..e9f65d2cdc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateFormatter.java
@@ -68,7 +68,7 @@ public class GitDateFormatter {
/**
* Git and JGit formats
*/
- static public enum Format {
+ public enum Format {
/**
* Git format: Time and original time zone
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 56a173163d..c6a6899948 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java
@@ -144,7 +144,7 @@ public class GitDateParser {
* <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 seperate the words</li>
+ * ' 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>
@@ -186,7 +186,7 @@ public class GitDateParser {
* <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 seperate the words</li>
+ * ' 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>
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 640670debc..d897255062 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
@@ -51,6 +51,7 @@ import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.Proxy;
import java.net.ProxySelector;
+import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
@@ -299,7 +300,9 @@ public class HttpSupport {
public static Proxy proxyFor(ProxySelector proxySelector, URL u)
throws ConnectException {
try {
- return proxySelector.select(u.toURI()).get(0);
+ URI uri = new URI(u.getProtocol(), null, u.getHost(), u.getPort(),
+ null, null, null);
+ return proxySelector.select(uri).get(0);
} catch (URISyntaxException e) {
final ConnectException err;
err = new ConnectException(MessageFormat.format(JGitText.get().cannotDetermineProxyFor, u));
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 a07a4fd1a5..391598d8ae 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/IO.java
@@ -345,13 +345,14 @@ public class IO {
c = s.charAt(++i);
l.add(sb.toString());
sb.setLength(0);
- if (c != '\n')
+ if (c != '\n') {
sb.append(c);
+ }
continue;
- } else { // EOF
- l.add(sb.toString());
- break;
}
+ // EOF
+ l.add(sb.toString());
+ break;
}
sb.append(c);
}
@@ -401,20 +402,18 @@ public class IO {
}
resetAndSkipFully(in, n);
}
- } else {
- StringBuilder buf = sizeHint > 0
- ? new StringBuilder(sizeHint)
- : new StringBuilder();
- int i;
- while ((i = in.read()) != -1) {
- char c = (char) i;
- buf.append(c);
- if (c == '\n') {
- break;
- }
+ }
+ StringBuilder buf = sizeHint > 0 ? new StringBuilder(sizeHint)
+ : new StringBuilder();
+ int i;
+ while ((i = in.read()) != -1) {
+ char c = (char) i;
+ buf.append(c);
+ if (c == '\n') {
+ break;
}
- return buf.toString();
}
+ return buf.toString();
}
private static void resetAndSkipFully(Reader fd, long toSkip) throws IOException {
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 96636b7994..0f6620e815 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java
@@ -145,7 +145,7 @@ public class LfsFactory {
}
/**
- * Retrieve a pre-push hook to be applied.
+ * Retrieve a pre-push hook to be applied using the default error stream.
*
* @param repo
* the {@link Repository} the hook is applied to.
@@ -159,6 +159,22 @@ public class LfsFactory {
}
/**
+ * Retrieve a pre-push hook to be applied.
+ *
+ * @param repo
+ * the {@link Repository} the hook is applied to.
+ * @param outputStream
+ * @param errorStream
+ * @return a {@link PrePushHook} implementation or <code>null</code>
+ * @since 5.6
+ */
+ @Nullable
+ public PrePushHook getPrePushHook(Repository repo, PrintStream outputStream,
+ PrintStream errorStream) {
+ return getPrePushHook(repo, outputStream);
+ }
+
+ /**
* Retrieve an {@link LfsInstallCommand} which can be used to enable LFS
* support (if available) either per repository or for the user.
*
@@ -297,7 +313,7 @@ public class LfsFactory {
}
@Override
- public int read(byte b[], int off, int len) throws IOException {
+ public int read(byte[] b, int off, int len) throws IOException {
return stream.read(b, off, len);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Monitoring.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Monitoring.java
index 83bf695f70..500c236730 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/Monitoring.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Monitoring.java
@@ -33,7 +33,7 @@ import org.slf4j.LoggerFactory;
* @since 5.1.13
*/
public class Monitoring {
- private final static Logger LOG = LoggerFactory.getLogger(Monitoring.class);
+ private static final Logger LOG = LoggerFactory.getLogger(Monitoring.class);
/**
* Register a MBean with the platform MBean server
@@ -49,7 +49,7 @@ public class Monitoring {
String metricName) {
boolean register = false;
try {
- Class<?> interfaces[] = mbean.getClass().getInterfaces();
+ Class<?>[] interfaces = mbean.getClass().getInterfaces();
for (Class<?> i : interfaces) {
register = SystemReader.getInstance().getUserConfig()
.getBoolean(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java
index 9267a325f4..1180d4c2ac 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ProcessResult.java
@@ -51,7 +51,7 @@ public class ProcessResult {
/**
* Status of a process' execution.
*/
- public static enum Status {
+ public enum Status {
/**
* The script was found and launched properly. It may still have exited
* with a non-zero {@link #exitCode}.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java
index a55cad3705..2b2358abe9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/QuotedString.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008, Google Inc.
+ * Copyright (C) 2008, 2019 Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -54,7 +54,15 @@ import org.eclipse.jgit.lib.Constants;
*/
public abstract class QuotedString {
/** Quoting style that obeys the rules Git applies to file names */
- public static final GitPathStyle GIT_PATH = new GitPathStyle();
+ public static final GitPathStyle GIT_PATH = new GitPathStyle(true);
+
+ /**
+ * Quoting style that obeys the rules Git applies to file names when
+ * {@code core.quotePath = false}.
+ *
+ * @since 5.6
+ */
+ public static final QuotedString GIT_PATH_MINIMAL = new GitPathStyle(false);
/**
* Quoting style used by the Bourne shell.
@@ -256,40 +264,48 @@ public abstract class QuotedString {
quote['"'] = '"';
}
+ private final boolean quoteHigh;
+
@Override
public String quote(String instr) {
- if (instr.length() == 0)
+ if (instr.isEmpty()) {
return "\"\""; //$NON-NLS-1$
+ }
boolean reuse = true;
final byte[] in = Constants.encode(instr);
- final StringBuilder r = new StringBuilder(2 + in.length);
- r.append('"');
+ final byte[] out = new byte[4 * in.length + 2];
+ int o = 0;
+ out[o++] = '"';
for (int i = 0; i < in.length; i++) {
final int c = in[i] & 0xff;
if (c < quote.length) {
final byte style = quote[c];
if (style == 0) {
- r.append((char) c);
+ out[o++] = (byte) c;
continue;
}
if (style > 0) {
reuse = false;
- r.append('\\');
- r.append((char) style);
+ out[o++] = '\\';
+ out[o++] = style;
continue;
}
+ } else if (!quoteHigh) {
+ out[o++] = (byte) c;
+ continue;
}
reuse = false;
- r.append('\\');
- r.append((char) (((c >> 6) & 03) + '0'));
- r.append((char) (((c >> 3) & 07) + '0'));
- r.append((char) (((c >> 0) & 07) + '0'));
+ out[o++] = '\\';
+ out[o++] = (byte) (((c >> 6) & 03) + '0');
+ out[o++] = (byte) (((c >> 3) & 07) + '0');
+ out[o++] = (byte) (((c >> 0) & 07) + '0');
}
- if (reuse)
+ if (reuse) {
return instr;
- r.append('"');
- return r.toString();
+ }
+ out[o++] = '"';
+ return new String(out, 0, o, UTF_8);
}
@Override
@@ -375,8 +391,8 @@ public abstract class QuotedString {
return RawParseUtils.decode(UTF_8, r, 0, rPtr);
}
- private GitPathStyle() {
- // Singleton
+ private GitPathStyle(boolean doQuote) {
+ quoteHigh = doQuote;
}
}
}
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 9663e3cef5..ce1308f334 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RefMap.java
@@ -191,12 +191,11 @@ public class RefMap extends AbstractMap<String, Ref> {
Ref prior = loose.get(name);
loose = loose.set(idx, value);
return prior;
- } else {
- Ref prior = get(keyName);
- loose = loose.add(idx, value);
- sizeIsValid = false;
- return prior;
}
+ Ref prior = get(keyName);
+ loose = loose.add(idx, value);
+ sizeIsValid = false;
+ return prior;
}
/** {@inheritDoc} */
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 83c60c63bc..55f39c265d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RelativeDateFormatter.java
@@ -52,19 +52,19 @@ import org.eclipse.jgit.internal.JGitText;
* in the format defined by {@code git log --relative-date}.
*/
public class RelativeDateFormatter {
- final static long SECOND_IN_MILLIS = 1000;
+ static final long SECOND_IN_MILLIS = 1000;
- final static long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
+ static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
- final static long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
+ static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
- final static long DAY_IN_MILLIS = 24 * HOUR_IN_MILLIS;
+ static final long DAY_IN_MILLIS = 24 * HOUR_IN_MILLIS;
- final static long WEEK_IN_MILLIS = 7 * DAY_IN_MILLIS;
+ static final long WEEK_IN_MILLIS = 7 * DAY_IN_MILLIS;
- final static long MONTH_IN_MILLIS = 30 * DAY_IN_MILLIS;
+ static final long MONTH_IN_MILLIS = 30 * DAY_IN_MILLIS;
- final static long YEAR_IN_MILLIS = 365 * DAY_IN_MILLIS;
+ static final long YEAR_IN_MILLIS = 365 * DAY_IN_MILLIS;
/**
* Get age of given {@link java.util.Date} compared to now formatted in the
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 e16a88655c..87ce4752b7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
@@ -85,7 +85,7 @@ import org.slf4j.LoggerFactory;
*/
public abstract class SystemReader {
- private final static Logger LOG = LoggerFactory
+ private static final Logger LOG = LoggerFactory
.getLogger(SystemReader.class);
private static final SystemReader DEFAULT;
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 9ab2caa1ac..e437c1114b 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
@@ -98,15 +98,16 @@ public abstract class LimitedInputStream extends FilterInputStream {
@Override
public int read() throws IOException {
if (left == 0) {
- if (in.available() == 0)
+ if (in.available() == 0) {
return -1;
- else
- limitExceeded();
+ }
+ limitExceeded();
}
int result = in.read();
- if (result != -1)
+ if (result != -1) {
--left;
+ }
return result;
}
@@ -114,16 +115,17 @@ public abstract class LimitedInputStream extends FilterInputStream {
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (left == 0) {
- if (in.available() == 0)
+ if (in.available() == 0) {
return -1;
- else
- limitExceeded();
+ }
+ limitExceeded();
}
len = (int) Math.min(len, left);
int result = in.read(b, off, len);
- if (result != -1)
+ if (result != -1) {
left -= result;
+ }
return result;
}
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 1ad6602fce..e6971ee205 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
@@ -78,7 +78,7 @@ import org.slf4j.LoggerFactory;
* @since 4.7
*/
public class SHA1 {
- private static Logger LOG = LoggerFactory.getLogger(SHA1.class);
+ private static final Logger LOG = LoggerFactory.getLogger(SHA1.class);
private static final boolean DETECT_COLLISIONS;
static {