Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Sohn2016-01-21 16:03:20 +0000
committerMatthias Sohn2016-01-21 16:07:31 +0000
commit4ec84fac86c9652847630efc2dda3e69954fdb61 (patch)
tree537cf6ffb986dc361f25a91296c04c4b5599b705 /org.eclipse.jgit/src
parent7e8e4ec019f4ca4d9a1892c7c882eba6013fdeaa (diff)
parent7b6122908b2aa31555cee3e0cc9dde304e0d90b3 (diff)
downloadjgit-4ec84fac86c9652847630efc2dda3e69954fdb61.tar.gz
jgit-4ec84fac86c9652847630efc2dda3e69954fdb61.tar.xz
jgit-4ec84fac86c9652847630efc2dda3e69954fdb61.zip
Merge branch 'master' into stable-4.2
Change-Id: Ieec4f51aedadf5734ae0e3f4e8713248a3c4fc52 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
Diffstat (limited to 'org.eclipse.jgit/src')
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java130
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java44
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java27
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java42
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java19
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java62
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java76
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java180
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java19
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java41
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/errors/DirCacheNameConflictException.java (renamed from org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java)55
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java32
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java61
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsObjDatabase.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackCompactor.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRefDatabase.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java37
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java140
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java106
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java70
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java19
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java49
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java52
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/AlwaysFailUpdate.java98
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java316
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java411
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java222
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java337
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeNames.java124
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeRename.java121
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeUpdate.java176
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java286
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java115
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java685
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java36
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSet.java (renamed from org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java)47
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java17
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java26
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java601
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java256
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java26
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java65
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java39
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java84
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java17
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java149
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java22
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java19
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java129
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java30
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java37
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java48
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java27
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java188
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java90
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java22
98 files changed, 4608 insertions, 1938 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
index 67fb342fe2..3b94f16f1a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java
@@ -43,6 +43,10 @@
*/
package org.eclipse.jgit.api;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.FileMode.GITLINK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
+
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
@@ -58,12 +62,12 @@ import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.treewalk.FileTreeIterator;
-import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
@@ -135,15 +139,12 @@ public class AddCommand extends GitCommand<DirCache> {
throw new NoFilepatternException(JGitText.get().atLeastOnePatternIsRequired);
checkCallable();
DirCache dc = null;
- boolean addAll = false;
- if (filepatterns.contains(".")) //$NON-NLS-1$
- addAll = true;
+ boolean addAll = filepatterns.contains("."); //$NON-NLS-1$
try (ObjectInserter inserter = repo.newObjectInserter();
- final TreeWalk tw = new TreeWalk(repo)) {
+ NameConflictTreeWalk tw = new NameConflictTreeWalk(repo)) {
tw.setOperationType(OperationType.CHECKIN_OP);
dc = repo.lockDirCache();
- DirCacheIterator c;
DirCacheBuilder builder = dc.builder();
tw.addTree(new DirCacheBuildIterator(builder));
@@ -151,62 +152,85 @@ public class AddCommand extends GitCommand<DirCache> {
workingTreeIterator = new FileTreeIterator(repo);
workingTreeIterator.setDirCacheIterator(tw, 0);
tw.addTree(workingTreeIterator);
- tw.setRecursive(true);
if (!addAll)
tw.setFilter(PathFilterGroup.createFromStrings(filepatterns));
- String lastAddedFile = null;
+ byte[] lastAdded = null;
while (tw.next()) {
- String path = tw.getPathString();
-
+ DirCacheIterator c = tw.getTree(0, DirCacheIterator.class);
WorkingTreeIterator f = tw.getTree(1, WorkingTreeIterator.class);
- if (tw.getTree(0, DirCacheIterator.class) == null &&
- f != null && f.isEntryIgnored()) {
+ if (c == null && f != null && f.isEntryIgnored()) {
// file is not in index but is ignored, do nothing
+ continue;
+ } else if (c == null && update) {
+ // Only update of existing entries was requested.
+ continue;
+ }
+
+ DirCacheEntry entry = c != null ? c.getDirCacheEntry() : null;
+ if (entry != null && entry.getStage() > 0
+ && lastAdded != null
+ && lastAdded.length == tw.getPathLength()
+ && tw.isPathPrefix(lastAdded, lastAdded.length) == 0) {
+ // In case of an existing merge conflict the
+ // DirCacheBuildIterator iterates over all stages of
+ // this path, we however want to add only one
+ // new DirCacheEntry per path.
+ continue;
+ }
+
+ if (tw.isSubtree() && !tw.isDirectoryFileConflict()) {
+ tw.enterSubtree();
+ continue;
+ }
+
+ if (f == null) { // working tree file does not exist
+ if (entry != null
+ && (!update || GITLINK == entry.getFileMode())) {
+ builder.add(entry);
+ }
+ continue;
+ }
+
+ if (entry != null && entry.isAssumeValid()) {
+ // Index entry is marked assume valid. Even though
+ // the user specified the file to be added JGit does
+ // not consider the file for addition.
+ builder.add(entry);
+ continue;
+ }
+
+ if (f.getEntryRawMode() == TYPE_TREE) {
+ // Index entry exists and is symlink, gitlink or file,
+ // otherwise the tree would have been entered above.
+ // Replace the index entry by diving into tree of files.
+ tw.enterSubtree();
+ continue;
+ }
+
+ byte[] path = tw.getRawPath();
+ if (entry == null || entry.getStage() > 0) {
+ entry = new DirCacheEntry(path);
}
- // In case of an existing merge conflict the
- // DirCacheBuildIterator iterates over all stages of
- // this path, we however want to add only one
- // new DirCacheEntry per path.
- else if (!(path.equals(lastAddedFile))) {
- if (!(update && tw.getTree(0, DirCacheIterator.class) == null)) {
- c = tw.getTree(0, DirCacheIterator.class);
- if (f != null) { // the file exists
- long sz = f.getEntryLength();
- DirCacheEntry entry = new DirCacheEntry(path);
- if (c == null || c.getDirCacheEntry() == null
- || !c.getDirCacheEntry().isAssumeValid()) {
- FileMode mode = f.getIndexFileMode(c);
- entry.setFileMode(mode);
-
- if (FileMode.GITLINK != mode) {
- entry.setLength(sz);
- entry.setLastModified(f
- .getEntryLastModified());
- long contentSize = f
- .getEntryContentLength();
- InputStream in = f.openEntryStream();
- try {
- entry.setObjectId(inserter.insert(
- Constants.OBJ_BLOB, contentSize, in));
- } finally {
- in.close();
- }
- } else
- entry.setObjectId(f.getEntryObjectId());
- builder.add(entry);
- lastAddedFile = path;
- } else {
- builder.add(c.getDirCacheEntry());
- }
-
- } else if (c != null
- && (!update || FileMode.GITLINK == c
- .getEntryFileMode()))
- builder.add(c.getDirCacheEntry());
+ FileMode mode = f.getIndexFileMode(c);
+ entry.setFileMode(mode);
+
+ if (GITLINK != mode) {
+ entry.setLength(f.getEntryLength());
+ entry.setLastModified(f.getEntryLastModified());
+ long len = f.getEntryContentLength();
+ try (InputStream in = f.openEntryStream()) {
+ ObjectId id = inserter.insert(OBJ_BLOB, len, in);
+ entry.setObjectId(id);
}
+ } else {
+ entry.setLength(0);
+ entry.setLastModified(0);
+ entry.setObjectId(f.getEntryObjectId());
}
+ builder.add(entry);
+ lastAdded = path;
}
inserter.flush();
builder.commit();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
index 6a945e4d39..676ae03009 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ApplyCommand.java
@@ -47,6 +47,7 @@ import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
@@ -141,9 +142,13 @@ public class ApplyCommand extends GitCommand<ApplyResult> {
case RENAME:
f = getFile(fh.getOldPath(), false);
File dest = getFile(fh.getNewPath(), false);
- if (!f.renameTo(dest))
+ try {
+ FileUtils.rename(f, dest,
+ StandardCopyOption.ATOMIC_MOVE);
+ } catch (IOException e) {
throw new PatchApplyException(MessageFormat.format(
- JGitText.get().renameFileFailed, f, dest));
+ JGitText.get().renameFileFailed, f, dest), e);
+ }
break;
case COPY:
f = getFile(fh.getOldPath(), false);
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 8743ea9ac7..4f918fa357 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -331,9 +331,16 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
private String getShortBranchName(Ref headRef) {
- if (headRef.getTarget().getName().equals(headRef.getName()))
- return headRef.getTarget().getObjectId().getName();
- return Repository.shortenRefName(headRef.getTarget().getName());
+ if (headRef.isSymbolic()) {
+ return Repository.shortenRefName(headRef.getTarget().getName());
+ }
+ // Detached HEAD. Every non-symbolic ref in the ref database has an
+ // object id, so this cannot be null.
+ ObjectId id = headRef.getObjectId();
+ if (id == null) {
+ throw new NullPointerException();
+ }
+ return id.getName();
}
/**
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 b3bc319aef..2ac8729507 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -61,6 +61,7 @@ import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
@@ -235,7 +236,7 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
}
if (head == null || head.getObjectId() == null)
- return; // throw exception?
+ return; // TODO throw exception?
if (head.getName().startsWith(Constants.R_HEADS)) {
final RefUpdate newHead = clonedRepo.updateRef(Constants.HEAD);
@@ -287,20 +288,24 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
private Ref findBranchToCheckout(FetchResult result) {
final Ref idHEAD = result.getAdvertisedRef(Constants.HEAD);
- if (idHEAD == null)
+ ObjectId headId = idHEAD != null ? idHEAD.getObjectId() : null;
+ if (headId == null) {
return null;
+ }
Ref master = result.getAdvertisedRef(Constants.R_HEADS
+ Constants.MASTER);
- if (master != null && master.getObjectId().equals(idHEAD.getObjectId()))
+ ObjectId objectId = master != null ? master.getObjectId() : null;
+ if (headId.equals(objectId)) {
return master;
+ }
Ref foundBranch = null;
for (final Ref r : result.getAdvertisedRefs()) {
final String n = r.getName();
if (!n.startsWith(Constants.R_HEADS))
continue;
- if (r.getObjectId().equals(idHEAD.getObjectId())) {
+ if (headId.equals(r.getObjectId())) {
foundBranch = r;
break;
}
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 6828ed338f..b5057ad282 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java
@@ -53,6 +53,7 @@ import java.util.List;
import org.eclipse.jgit.api.errors.AbortedByHookException;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
+import org.eclipse.jgit.api.errors.EmtpyCommitException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoFilepatternException;
@@ -130,6 +131,8 @@ public class CommitCommand extends GitCommand<RevCommit> {
private PrintStream hookOutRedirect;
+ private Boolean allowEmpty;
+
/**
* @param repo
*/
@@ -231,6 +234,16 @@ public class CommitCommand extends GitCommand<RevCommit> {
if (insertChangeId)
insertChangeId(indexTreeId);
+ // Check for empty commits
+ if (headId != null && !allowEmpty.booleanValue()) {
+ RevCommit headCommit = rw.parseCommit(headId);
+ headCommit.getTree();
+ if (indexTreeId.equals(headCommit.getTree())) {
+ throw new EmtpyCommitException(
+ JGitText.get().emptyCommit);
+ }
+ }
+
// Create a Commit object, populate it and write it
CommitBuilder commit = new CommitBuilder();
commit.setCommitter(committer);
@@ -457,6 +470,8 @@ public class CommitCommand extends GitCommand<RevCommit> {
// there must be at least one change
if (emptyCommit)
+ // Would like to throw a EmptyCommitException. But this would break the API
+ // TODO(ch): Change this in the next release
throw new JGitInternalException(JGitText.get().emptyCommit);
// update index
@@ -510,6 +525,12 @@ public class CommitCommand extends GitCommand<RevCommit> {
committer = new PersonIdent(repo);
if (author == null && !amend)
author = committer;
+ if (allowEmpty == null)
+ // JGit allows empty commits by default. Only when pathes are
+ // specified the commit should not be empty. This behaviour differs
+ // from native git but can only be adapted in the next release.
+ // TODO(ch) align the defaults with native git
+ allowEmpty = (only.isEmpty()) ? Boolean.TRUE : Boolean.FALSE;
// when doing a merge commit parse MERGE_HEAD and MERGE_MSG files
if (state == RepositoryState.MERGING_RESOLVED
@@ -579,6 +600,27 @@ public class CommitCommand extends GitCommand<RevCommit> {
}
/**
+ * @param allowEmpty
+ * whether it should be allowed to create a commit which has the
+ * same tree as it's sole predecessor (a commit which doesn't
+ * change anything). By default when creating standard commits
+ * (without specifying paths) JGit allows to create such commits.
+ * When this flag is set to false an attempt to create an "empty"
+ * standard commit will lead to an EmptyCommitException.
+ * <p>
+ * By default when creating a commit containing only specified
+ * paths an attempt to create an empty commit leads to a
+ * {@link JGitInternalException}. By setting this flag to
+ * <code>true</code> this exception will not be thrown.
+ * @return {@code this}
+ * @since 4.2
+ */
+ public CommitCommand setAllowEmpty(boolean allowEmpty) {
+ this.allowEmpty = Boolean.valueOf(allowEmpty);
+ return this;
+ }
+
+ /**
* @return the commit message used for the <code>commit</code>
*/
public String getMessage() {
@@ -681,7 +723,7 @@ public class CommitCommand extends GitCommand<RevCommit> {
*/
public CommitCommand setAll(boolean all) {
checkCallable();
- if (!only.isEmpty())
+ if (all && !only.isEmpty())
throw new JGitInternalException(MessageFormat.format(
JGitText.get().illegalCombinationOfArguments, "--all", //$NON-NLS-1$
"--only")); //$NON-NLS-1$
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 9620089b08..de512761a4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
@@ -116,22 +116,17 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> {
org.eclipse.jgit.api.errors.TransportException {
checkCallable();
- try {
- Transport transport = Transport.open(repo, remote);
- try {
- transport.setCheckFetchedObjects(checkFetchedObjects);
- transport.setRemoveDeletedRefs(isRemoveDeletedRefs());
- transport.setDryRun(dryRun);
- if (tagOption != null)
- transport.setTagOpt(tagOption);
- transport.setFetchThin(thin);
- configure(transport);
-
- FetchResult result = transport.fetch(monitor, refSpecs);
- return result;
- } finally {
- transport.close();
- }
+ try (Transport transport = Transport.open(repo, remote)) {
+ transport.setCheckFetchedObjects(checkFetchedObjects);
+ transport.setRemoveDeletedRefs(isRemoveDeletedRefs());
+ transport.setDryRun(dryRun);
+ if (tagOption != null)
+ transport.setTagOpt(tagOption);
+ transport.setFetchThin(thin);
+ configure(transport);
+
+ FetchResult result = transport.fetch(monitor, refSpecs);
+ return result;
} catch (NoRemoteRepositoryException e) {
throw new InvalidRemoteException(MessageFormat.format(
JGitText.get().invalidRemote, remote), e);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java
index 3363a0fc8f..f3527fd805 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/LsRemoteCommand.java
@@ -182,13 +182,9 @@ public class LsRemoteCommand extends
org.eclipse.jgit.api.errors.TransportException {
checkCallable();
- Transport transport = null;
- FetchConnection fc = null;
- try {
- if (repo != null)
- transport = Transport.open(repo, remote);
- else
- transport = Transport.open(new URIish(remote));
+ try (Transport transport = repo != null
+ ? Transport.open(repo, remote)
+ : Transport.open(new URIish(remote))) {
transport.setOptionUploadPack(uploadPack);
configure(transport);
Collection<RefSpec> refSpecs = new ArrayList<RefSpec>(1);
@@ -199,19 +195,20 @@ public class LsRemoteCommand extends
refSpecs.add(new RefSpec("refs/heads/*:refs/remotes/origin/*")); //$NON-NLS-1$
Collection<Ref> refs;
Map<String, Ref> refmap = new HashMap<String, Ref>();
- fc = transport.openFetch();
- refs = fc.getRefs();
- if (refSpecs.isEmpty())
- for (Ref r : refs)
- refmap.put(r.getName(), r);
- else
- for (Ref r : refs)
- for (RefSpec rs : refSpecs)
- if (rs.matchSource(r)) {
- refmap.put(r.getName(), r);
- break;
- }
- return refmap;
+ try (FetchConnection fc = transport.openFetch()) {
+ refs = fc.getRefs();
+ if (refSpecs.isEmpty())
+ for (Ref r : refs)
+ refmap.put(r.getName(), r);
+ else
+ for (Ref r : refs)
+ for (RefSpec rs : refSpecs)
+ if (rs.matchSource(r)) {
+ refmap.put(r.getName(), r);
+ break;
+ }
+ return refmap;
+ }
} catch (URISyntaxException e) {
throw new InvalidRemoteException(MessageFormat.format(
JGitText.get().invalidRemote, remote));
@@ -223,11 +220,6 @@ public class LsRemoteCommand extends
throw new org.eclipse.jgit.api.errors.TransportException(
e.getMessage(),
e);
- } finally {
- if (fc != null)
- fc.close();
- if (transport != null)
- transport.close();
}
}
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 8582bbb0dc..e3e76c95f4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -560,6 +560,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
lastStepWasForward = newHead != null;
if (!lastStepWasForward) {
ObjectId headId = getHead().getObjectId();
+ // getHead() checks for null
+ assert headId != null;
if (!AnyObjectId.equals(headId, newParents.get(0)))
checkoutCommit(headId.getName(), newParents.get(0));
@@ -674,6 +676,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
return;
ObjectId headId = getHead().getObjectId();
+ // getHead() checks for null
+ assert headId != null;
String head = headId.getName();
String currentCommits = rebaseState.readFile(CURRENT_COMMIT);
for (String current : currentCommits.split("\n")) //$NON-NLS-1$
@@ -1073,11 +1077,12 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
Ref head = getHead();
- String headName = getHeadName(head);
ObjectId headId = head.getObjectId();
- if (headId == null)
+ if (headId == null) {
throw new RefNotFoundException(MessageFormat.format(
JGitText.get().refNotResolved, Constants.HEAD));
+ }
+ String headName = getHeadName(head);
RevCommit headCommit = walk.lookupCommit(headId);
RevCommit upstream = walk.lookupCommit(upstreamCommit.getId());
@@ -1188,10 +1193,14 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
private static String getHeadName(Ref head) {
String headName;
- if (head.isSymbolic())
+ if (head.isSymbolic()) {
headName = head.getTarget().getName();
- else
- headName = head.getObjectId().getName();
+ } else {
+ ObjectId headId = head.getObjectId();
+ // the callers are checking this already
+ assert headId != null;
+ headName = headId.getName();
+ }
return headName;
}
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 8f4bc4f26c..4c91e6c17f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
@@ -190,10 +190,8 @@ public class ResetCommand extends GitCommand<Ref> {
ObjectId origHead = ru.getOldObjectId();
if (origHead != null)
repo.writeOrigHead(origHead);
- result = ru.getRef();
- } else {
- result = repo.getRef(Constants.HEAD);
}
+ result = repo.exactRef(Constants.HEAD);
if (mode == null)
mode = ResetType.MIXED;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
index 7923fd49be..f6903be05c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashDropCommand.java
@@ -46,6 +46,7 @@ import static org.eclipse.jgit.lib.Constants.R_STASH;
import java.io.File;
import java.io.IOException;
+import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
import java.util.List;
@@ -220,12 +221,14 @@ public class StashDropCommand extends GitCommand<ObjectId> {
entry.getWho(), entry.getComment());
entryId = entry.getNewId();
}
- if (!stashLockFile.renameTo(stashFile)) {
- FileUtils.delete(stashFile);
- if (!stashLockFile.renameTo(stashFile))
+ try {
+ FileUtils.rename(stashLockFile, stashFile,
+ StandardCopyOption.ATOMIC_MOVE);
+ } catch (IOException e) {
throw new JGitInternalException(MessageFormat.format(
JGitText.get().renameFileFailed,
- stashLockFile.getPath(), stashFile.getPath()));
+ stashLockFile.getPath(), stashFile.getPath()),
+ e);
}
} catch (IOException e) {
throw new JGitInternalException(JGitText.get().stashDropFailed, e);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java
index 1aeb6109ec..3d2e46b26e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/TransportCommand.java
@@ -79,6 +79,7 @@ public abstract class TransportCommand<C extends GitCommand, T> extends
*/
protected TransportCommand(final Repository repo) {
super(repo);
+ setCredentialsProvider(CredentialsProvider.getDefault());
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java
new file mode 100644
index 0000000000..b3cc1bfcf2
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/EmtpyCommitException.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v1.0 which accompanies this
+ * distribution, is reproduced below, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.api.errors;
+
+/**
+ * Exception thrown when a newly created commit does not contain any changes
+ *
+ * @since 4.2
+ */
+public class EmtpyCommitException extends GitAPIException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public EmtpyCommitException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * @param message
+ */
+ public EmtpyCommitException(String message) {
+ super(message);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java
index 70f80aeb7a..0fbc1f8acf 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/BaseDirCacheEditor.java
@@ -44,8 +44,13 @@
package org.eclipse.jgit.dircache;
+import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
+import static org.eclipse.jgit.util.Paths.compareSameName;
+
import java.io.IOException;
+import org.eclipse.jgit.errors.DirCacheNameConflictException;
+
/**
* Generic update/editing support for {@link DirCache}.
* <p>
@@ -168,6 +173,7 @@ abstract class BaseDirCacheEditor {
* {@link #finish()}, and only after {@link #entries} is sorted.
*/
protected void replace() {
+ checkNameConflicts();
if (entryCnt < entries.length / 2) {
final DirCacheEntry[] n = new DirCacheEntry[entryCnt];
System.arraycopy(entries, 0, n, 0, entryCnt);
@@ -176,6 +182,76 @@ abstract class BaseDirCacheEditor {
cache.replace(entries, entryCnt);
}
+ private void checkNameConflicts() {
+ int end = entryCnt - 1;
+ for (int eIdx = 0; eIdx < end; eIdx++) {
+ DirCacheEntry e = entries[eIdx];
+ if (e.getStage() != 0) {
+ continue;
+ }
+
+ byte[] ePath = e.path;
+ int prefixLen = lastSlash(ePath) + 1;
+
+ for (int nIdx = eIdx + 1; nIdx < entryCnt; nIdx++) {
+ DirCacheEntry n = entries[nIdx];
+ if (n.getStage() != 0) {
+ continue;
+ }
+
+ byte[] nPath = n.path;
+ if (!startsWith(ePath, nPath, prefixLen)) {
+ // Different prefix; this entry is in another directory.
+ break;
+ }
+
+ int s = nextSlash(nPath, prefixLen);
+ int m = s < nPath.length ? TYPE_TREE : n.getRawMode();
+ int cmp = compareSameName(
+ ePath, prefixLen, ePath.length,
+ nPath, prefixLen, s, m);
+ if (cmp < 0) {
+ break;
+ } else if (cmp == 0) {
+ throw new DirCacheNameConflictException(
+ e.getPathString(),
+ n.getPathString());
+ }
+ }
+ }
+ }
+
+ private static int lastSlash(byte[] path) {
+ for (int i = path.length - 1; i >= 0; i--) {
+ if (path[i] == '/') {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private static int nextSlash(byte[] b, int p) {
+ final int n = b.length;
+ for (; p < n; p++) {
+ if (b[p] == '/') {
+ return p;
+ }
+ }
+ return n;
+ }
+
+ private static boolean startsWith(byte[] a, byte[] b, int n) {
+ if (b.length < n) {
+ return false;
+ }
+ for (n--; n >= 0; n--) {
+ if (a[n] != b[n]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* Finish, write, commit this change, and release the index lock.
* <p>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
index fa0339544f..ecdfe823a8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java
@@ -800,8 +800,11 @@ public class DirCache {
* information. If &lt; 0 the entry does not exist in the index.
* @since 3.4
*/
- public int findEntry(final byte[] p, final int pLen) {
- int low = 0;
+ public int findEntry(byte[] p, int pLen) {
+ return findEntry(0, p, pLen);
+ }
+
+ int findEntry(int low, byte[] p, int pLen) {
int high = entryCnt;
while (low < high) {
int mid = (low + high) >>> 1;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java
index da55306665..c10e416082 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuildIterator.java
@@ -130,4 +130,9 @@ public class DirCacheBuildIterator extends DirCacheIterator {
if (cur < cnt)
builder.keep(cur, cnt - cur);
}
+
+ @Override
+ protected boolean needsStopWalk() {
+ return ptr < cache.getEntryCount();
+ }
}
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 4eb688170c..a1e1d15ac6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -46,6 +46,7 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
@@ -1319,11 +1320,12 @@ public class DirCacheCheckout {
if (deleteRecursive && f.isDirectory()) {
FileUtils.delete(f, FileUtils.RECURSIVE);
}
- FileUtils.rename(tmpFile, f);
+ FileUtils.rename(tmpFile, f, StandardCopyOption.ATOMIC_MOVE);
} catch (IOException e) {
- throw new IOException(MessageFormat.format(
- JGitText.get().renameFileFailed, tmpFile.getPath(),
- f.getPath()));
+ throw new IOException(
+ MessageFormat.format(JGitText.get().renameFileFailed,
+ tmpFile.getPath(), f.getPath()),
+ e);
} finally {
if (tmpFile.exists()) {
FileUtils.delete(tmpFile);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
index 13885d370c..c987c964c4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEditor.java
@@ -44,6 +44,10 @@
package org.eclipse.jgit.dircache;
+import static org.eclipse.jgit.dircache.DirCache.cmp;
+import static org.eclipse.jgit.dircache.DirCacheTree.peq;
+import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
+
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
@@ -53,6 +57,7 @@ import java.util.List;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.util.Paths;
/**
* Updates a {@link DirCache} by supplying discrete edit commands.
@@ -72,11 +77,12 @@ public class DirCacheEditor extends BaseDirCacheEditor {
public int compare(final PathEdit o1, final PathEdit o2) {
final byte[] a = o1.path;
final byte[] b = o2.path;
- return DirCache.cmp(a, a.length, b, b.length);
+ return cmp(a, a.length, b, b.length);
}
};
private final List<PathEdit> edits;
+ private int editIdx;
/**
* Construct a new editor.
@@ -126,35 +132,44 @@ public class DirCacheEditor extends BaseDirCacheEditor {
private void applyEdits() {
Collections.sort(edits, EDIT_CMP);
+ editIdx = 0;
final int maxIdx = cache.getEntryCount();
int lastIdx = 0;
- for (final PathEdit e : edits) {
- int eIdx = cache.findEntry(e.path, e.path.length);
+ while (editIdx < edits.size()) {
+ PathEdit e = edits.get(editIdx++);
+ int eIdx = cache.findEntry(lastIdx, e.path, e.path.length);
final boolean missing = eIdx < 0;
if (eIdx < 0)
eIdx = -(eIdx + 1);
final int cnt = Math.min(eIdx, maxIdx) - lastIdx;
if (cnt > 0)
fastKeep(lastIdx, cnt);
- lastIdx = missing ? eIdx : cache.nextEntry(eIdx);
- if (e instanceof DeletePath)
+ if (e instanceof DeletePath) {
+ lastIdx = missing ? eIdx : cache.nextEntry(eIdx);
continue;
+ }
if (e instanceof DeleteTree) {
lastIdx = cache.nextEntry(e.path, e.path.length, eIdx);
continue;
}
if (missing) {
- final DirCacheEntry ent = new DirCacheEntry(e.path);
+ DirCacheEntry ent = new DirCacheEntry(e.path);
e.apply(ent);
- if (ent.getRawMode() == 0)
- throw new IllegalArgumentException(MessageFormat.format(JGitText.get().fileModeNotSetForPath
- , ent.getPathString()));
+ if (ent.getRawMode() == 0) {
+ throw new IllegalArgumentException(MessageFormat.format(
+ JGitText.get().fileModeNotSetForPath,
+ ent.getPathString()));
+ }
+ lastIdx = e.replace
+ ? deleteOverlappingSubtree(ent, eIdx)
+ : eIdx;
fastAdd(ent);
} else {
// Apply to all entries of the current path (different stages)
+ lastIdx = cache.nextEntry(eIdx);
for (int i = eIdx; i < lastIdx; i++) {
final DirCacheEntry ent = cache.getEntry(i);
e.apply(ent);
@@ -168,6 +183,102 @@ public class DirCacheEditor extends BaseDirCacheEditor {
fastKeep(lastIdx, cnt);
}
+ private int deleteOverlappingSubtree(DirCacheEntry ent, int eIdx) {
+ byte[] entPath = ent.path;
+ int entLen = entPath.length;
+
+ // Delete any file that was previously processed and overlaps
+ // the parent directory for the new entry. Since the editor
+ // always processes entries in path order, binary search back
+ // for the overlap for each parent directory.
+ for (int p = pdir(entPath, entLen); p > 0; p = pdir(entPath, p)) {
+ int i = findEntry(entPath, p);
+ if (i >= 0) {
+ // A file does overlap, delete the file from the array.
+ // No other parents can have overlaps as the file should
+ // have taken care of that itself.
+ int n = --entryCnt - i;
+ System.arraycopy(entries, i + 1, entries, i, n);
+ break;
+ }
+
+ // If at least one other entry already exists in this parent
+ // directory there is no need to continue searching up the tree.
+ i = -(i + 1);
+ if (i < entryCnt && inDir(entries[i], entPath, p)) {
+ break;
+ }
+ }
+
+ int maxEnt = cache.getEntryCount();
+ if (eIdx >= maxEnt) {
+ return maxEnt;
+ }
+
+ DirCacheEntry next = cache.getEntry(eIdx);
+ if (Paths.compare(next.path, 0, next.path.length, 0,
+ entPath, 0, entLen, TYPE_TREE) < 0) {
+ // Next DirCacheEntry sorts before new entry as tree. Defer a
+ // DeleteTree command to delete any entries if they exist. This
+ // case only happens for A, A.c, A/c type of conflicts (rare).
+ insertEdit(new DeleteTree(entPath));
+ return eIdx;
+ }
+
+ // Next entry may be contained by the entry-as-tree, skip if so.
+ while (eIdx < maxEnt && inDir(cache.getEntry(eIdx), entPath, entLen)) {
+ eIdx++;
+ }
+ return eIdx;
+ }
+
+ private int findEntry(byte[] p, int pLen) {
+ int low = 0;
+ int high = entryCnt;
+ while (low < high) {
+ int mid = (low + high) >>> 1;
+ int cmp = cmp(p, pLen, entries[mid]);
+ if (cmp < 0) {
+ high = mid;
+ } else if (cmp == 0) {
+ while (mid > 0 && cmp(p, pLen, entries[mid - 1]) == 0) {
+ mid--;
+ }
+ return mid;
+ } else {
+ low = mid + 1;
+ }
+ }
+ return -(low + 1);
+ }
+
+ private void insertEdit(DeleteTree d) {
+ for (int i = editIdx; i < edits.size(); i++) {
+ int cmp = EDIT_CMP.compare(d, edits.get(i));
+ if (cmp < 0) {
+ edits.add(i, d);
+ return;
+ } else if (cmp == 0) {
+ return;
+ }
+ }
+ edits.add(d);
+ }
+
+ private static boolean inDir(DirCacheEntry e, byte[] path, int pLen) {
+ return e.path.length > pLen && e.path[pLen] == '/'
+ && peq(path, e.path, pLen);
+ }
+
+ private static int pdir(byte[] path, int e) {
+ for (e--; e > 0; e--) {
+ if (path[e] == '/') {
+ return e;
+ }
+ }
+ return 0;
+ }
+
/**
* Any index record update.
* <p>
@@ -179,6 +290,7 @@ public class DirCacheEditor extends BaseDirCacheEditor {
*/
public abstract static class PathEdit {
final byte[] path;
+ boolean replace = true;
/**
* Create a new update command by path name.
@@ -190,6 +302,10 @@ public class DirCacheEditor extends BaseDirCacheEditor {
path = Constants.encode(entryPath);
}
+ PathEdit(byte[] path) {
+ this.path = path;
+ }
+
/**
* Create a new update command for an existing entry instance.
*
@@ -202,6 +318,22 @@ public class DirCacheEditor extends BaseDirCacheEditor {
}
/**
+ * Configure if a file can replace a directory (or vice versa).
+ * <p>
+ * Default is {@code true} as this is usually the desired behavior.
+ *
+ * @param ok
+ * if true a file can replace a directory, or a directory can
+ * replace a file.
+ * @return {@code this}
+ * @since 4.2
+ */
+ public PathEdit setReplace(boolean ok) {
+ replace = ok;
+ return this;
+ }
+
+ /**
* Apply the update to a single cache entry matching the path.
* <p>
* After apply is invoked the entry is added to the output table, and
@@ -212,6 +344,12 @@ public class DirCacheEditor extends BaseDirCacheEditor {
* the path is a new path in the index.
*/
public abstract void apply(DirCacheEntry ent);
+
+ @Override
+ public String toString() {
+ String p = DirCacheEntry.toString(path);
+ return getClass().getSimpleName() + '[' + p + ']';
+ }
}
/**
@@ -272,10 +410,26 @@ public class DirCacheEditor extends BaseDirCacheEditor {
* only the subtree's contents are matched by the command.
* The special case "" (not "/"!) deletes all entries.
*/
- public DeleteTree(final String entryPath) {
- super(
- (entryPath.endsWith("/") || entryPath.length() == 0) ? entryPath //$NON-NLS-1$
- : entryPath + "/"); //$NON-NLS-1$
+ public DeleteTree(String entryPath) {
+ super(entryPath.isEmpty()
+ || entryPath.charAt(entryPath.length() - 1) == '/'
+ ? entryPath
+ : entryPath + '/');
+ }
+
+ DeleteTree(byte[] path) {
+ super(appendSlash(path));
+ }
+
+ private static byte[] appendSlash(byte[] path) {
+ int n = path.length;
+ if (n > 0 && path[n - 1] != '/') {
+ byte[] r = new byte[n + 1];
+ System.arraycopy(path, 0, r, 0, n);
+ r[n] = '/';
+ return r;
+ }
+ return path;
}
public void apply(final DirCacheEntry ent) {
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 c8bc0960f4..4ebf2e0d71 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java
@@ -294,6 +294,23 @@ public class DirCacheEntry {
NB.encodeInt16(info, infoOffset + P_FLAGS, flags);
}
+ /**
+ * Duplicate DirCacheEntry with same path and copied info.
+ * <p>
+ * The same path buffer is reused (avoiding copying), however a new info
+ * buffer is created and its contents are copied.
+ *
+ * @param src
+ * entry to clone.
+ * @since 4.2
+ */
+ public DirCacheEntry(DirCacheEntry src) {
+ path = src.path;
+ info = new byte[INFO_LEN];
+ infoOffset = 0;
+ System.arraycopy(src.info, src.infoOffset, info, 0, INFO_LEN);
+ }
+
void write(final OutputStream os) throws IOException {
final int len = isExtended() ? INFO_LEN_EXTENDED : INFO_LEN;
final int pathLen = path.length;
@@ -745,7 +762,7 @@ public class DirCacheEntry {
}
}
- private static String toString(final byte[] path) {
+ static String toString(final byte[] path) {
return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java
index c6ea093750..e4db40b889 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/CorruptObjectException.java
@@ -49,8 +49,10 @@ package org.eclipse.jgit.errors;
import java.io.IOException;
import java.text.MessageFormat;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectChecker;
import org.eclipse.jgit.lib.ObjectId;
/**
@@ -59,6 +61,26 @@ import org.eclipse.jgit.lib.ObjectId;
public class CorruptObjectException extends IOException {
private static final long serialVersionUID = 1L;
+ private ObjectChecker.ErrorType errorType;
+
+ /**
+ * Report a specific error condition discovered in an object.
+ *
+ * @param type
+ * type of error
+ * @param id
+ * identity of the bad object
+ * @param why
+ * description of the error.
+ * @since 4.2
+ */
+ public CorruptObjectException(ObjectChecker.ErrorType type, AnyObjectId id,
+ String why) {
+ super(MessageFormat.format(JGitText.get().objectIsCorrupt3,
+ type.getMessageId(), id.name(), why));
+ this.errorType = type;
+ }
+
/**
* Construct a CorruptObjectException for reporting a problem specified
* object id
@@ -66,8 +88,8 @@ public class CorruptObjectException extends IOException {
* @param id
* @param why
*/
- public CorruptObjectException(final AnyObjectId id, final String why) {
- this(id.toObjectId(), why);
+ public CorruptObjectException(AnyObjectId id, String why) {
+ super(MessageFormat.format(JGitText.get().objectIsCorrupt, id.name(), why));
}
/**
@@ -77,7 +99,7 @@ public class CorruptObjectException extends IOException {
* @param id
* @param why
*/
- public CorruptObjectException(final ObjectId id, final String why) {
+ public CorruptObjectException(ObjectId id, String why) {
super(MessageFormat.format(JGitText.get().objectIsCorrupt, id.name(), why));
}
@@ -87,7 +109,7 @@ public class CorruptObjectException extends IOException {
*
* @param why
*/
- public CorruptObjectException(final String why) {
+ public CorruptObjectException(String why) {
super(why);
}
@@ -105,4 +127,15 @@ public class CorruptObjectException extends IOException {
super(why);
initCause(cause);
}
+
+ /**
+ * Specific error condition identified by {@link ObjectChecker}.
+ *
+ * @return error condition or null.
+ * @since 4.2
+ */
+ @Nullable
+ public ObjectChecker.ErrorType getErrorType() {
+ return errorType;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/DirCacheNameConflictException.java
index 936fd82bfd..5f67e3439b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GitlinkTreeEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/DirCacheNameConflictException.java
@@ -1,7 +1,5 @@
/*
- * Copyright (C) 2009, Jonas Fonseca <fonseca@diku.dk>
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2007, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2015, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -43,45 +41,40 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package org.eclipse.jgit.lib;
+package org.eclipse.jgit.errors;
/**
- * A tree entry representing a gitlink entry used for submodules.
+ * Thrown by DirCache code when entries overlap in impossible way.
*
- * Note. Java cannot really handle these as file system objects.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
+ * @since 4.2
*/
-@Deprecated
-public class GitlinkTreeEntry extends TreeEntry {
+public class DirCacheNameConflictException extends IllegalStateException {
+ private static final long serialVersionUID = 1L;
+
+ private final String path1;
+ private final String path2;
/**
- * Construct a {@link GitlinkTreeEntry} with the specified name and SHA-1 in
- * the specified parent
+ * Construct an exception for a specific path.
*
- * @param parent
- * @param id
- * @param nameUTF8
+ * @param path1
+ * one path that conflicts.
+ * @param path2
+ * another path that conflicts.
*/
- public GitlinkTreeEntry(final Tree parent, final ObjectId id,
- final byte[] nameUTF8) {
- super(parent, id, nameUTF8);
+ public DirCacheNameConflictException(String path1, String path2) {
+ super(path1 + ' ' + path2);
+ this.path1 = path1;
+ this.path2 = path2;
}
- public FileMode getMode() {
- return FileMode.GITLINK;
+ /** @return one of the paths that has a conflict. */
+ public String getPath1() {
+ return path1;
}
- @Override
- public String toString() {
- final StringBuilder r = new StringBuilder();
- r.append(ObjectId.toString(getId()));
- r.append(" G "); //$NON-NLS-1$
- r.append(getFullName());
- return r.toString();
+ /** @return another path that has a conflict. */
+ public String getPath2() {
+ return path2;
}
}
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 891479d1f4..7eb955006e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/ManifestParser.java
@@ -338,6 +338,20 @@ public class ManifestParser extends DefaultHandler {
else
last = p;
}
+ removeNestedCopyfiles();
+ }
+
+ /** Remove copyfiles that sit in a subdirectory of any other project. */
+ void removeNestedCopyfiles() {
+ for (RepoProject proj : filteredProjects) {
+ List<CopyFile> copyfiles = new ArrayList<>(proj.getCopyFiles());
+ proj.clearCopyFiles();
+ for (CopyFile copyfile : copyfiles) {
+ if (!isNestedCopyfile(copyfile)) {
+ proj.addCopyFile(copyfile);
+ }
+ }
+ }
}
boolean inGroups(RepoProject proj) {
@@ -357,4 +371,22 @@ public class ManifestParser extends DefaultHandler {
}
return false;
}
+
+ private boolean isNestedCopyfile(CopyFile copyfile) {
+ if (copyfile.dest.indexOf('/') == -1) {
+ // If the copyfile is at root level then it won't be nested.
+ return false;
+ }
+ for (RepoProject proj : filteredProjects) {
+ if (proj.getPath().compareTo(copyfile.dest) > 0) {
+ // Early return as remaining projects can't be ancestor of this
+ // copyfile config (filteredProjects is sorted).
+ return false;
+ }
+ if (proj.isAncestorOf(copyfile.dest)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
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 9a072114a7..915066d58f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoProject.java
@@ -258,6 +258,15 @@ public class RepoProject implements Comparable<RepoProject> {
this.copyfiles.addAll(copyfiles);
}
+ /**
+ * Clear all the copyfiles.
+ *
+ * @since 4.2
+ */
+ public void clearCopyFiles() {
+ this.copyfiles.clear();
+ }
+
private String getPathWithSlash() {
if (path.endsWith("/")) //$NON-NLS-1$
return path;
@@ -273,7 +282,19 @@ public class RepoProject implements Comparable<RepoProject> {
* @return true if this sub repo is the ancestor of given sub repo.
*/
public boolean isAncestorOf(RepoProject that) {
- return that.getPathWithSlash().startsWith(this.getPathWithSlash());
+ return isAncestorOf(that.getPathWithSlash());
+ }
+
+ /**
+ * Check if this sub repo is an ancestor of the given path.
+ *
+ * @param path
+ * path to be checked to see if it is within this repository
+ * @return true if this sub repo is an ancestor of the given path.
+ * @since 4.2
+ */
+ public boolean isAncestorOf(String path) {
+ return path.startsWith(getPathWithSlash());
}
@Override
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 796eaaebf5..7740a2bb80 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -158,6 +158,7 @@ public class JGitText extends TranslationBundle {
/***/ public String cannotStoreObjects;
/***/ public String cannotResolveUniquelyAbbrevObjectId;
/***/ public String cannotUnloadAModifiedTree;
+ /***/ public String cannotUpdateUnbornBranch;
/***/ public String cannotWorkWithOtherStagesThanZeroRightNow;
/***/ public String cannotWriteObjectsPath;
/***/ public String canOnlyCherryPickCommitsWithOneParent;
@@ -184,14 +185,15 @@ public class JGitText extends TranslationBundle {
/***/ public String connectionTimeOut;
/***/ public String contextMustBeNonNegative;
/***/ public String corruptionDetectedReReadingAt;
+ /***/ public String corruptObjectBadDate;
+ /***/ public String corruptObjectBadEmail;
/***/ public String corruptObjectBadStream;
/***/ public String corruptObjectBadStreamCorruptHeader;
+ /***/ public String corruptObjectBadTimezone;
/***/ public String corruptObjectDuplicateEntryNames;
/***/ public String corruptObjectGarbageAfterSize;
/***/ public String corruptObjectIncorrectLength;
/***/ public String corruptObjectIncorrectSorting;
- /***/ public String corruptObjectInvalidAuthor;
- /***/ public String corruptObjectInvalidCommitter;
/***/ public String corruptObjectInvalidEntryMode;
/***/ public String corruptObjectInvalidMode;
/***/ public String corruptObjectInvalidModeChar;
@@ -210,11 +212,11 @@ public class JGitText extends TranslationBundle {
/***/ public String corruptObjectInvalidNamePrn;
/***/ public String corruptObjectInvalidObject;
/***/ public String corruptObjectInvalidParent;
- /***/ public String corruptObjectInvalidTagger;
/***/ public String corruptObjectInvalidTree;
/***/ public String corruptObjectInvalidType;
/***/ public String corruptObjectInvalidType2;
/***/ public String corruptObjectMalformedHeader;
+ /***/ public String corruptObjectMissingEmail;
/***/ public String corruptObjectNameContainsByte;
/***/ public String corruptObjectNameContainsChar;
/***/ public String corruptObjectNameContainsNullByte;
@@ -240,6 +242,7 @@ public class JGitText extends TranslationBundle {
/***/ public String corruptObjectTruncatedInMode;
/***/ public String corruptObjectTruncatedInName;
/***/ public String corruptObjectTruncatedInObjectId;
+ /***/ public String corruptObjectZeroId;
/***/ public String corruptPack;
/***/ public String couldNotCheckOutBecauseOfConflicts;
/***/ public String couldNotDeleteLockFileShouldNotHappen;
@@ -491,6 +494,7 @@ public class JGitText extends TranslationBundle {
/***/ public String objectAtHasBadZlibStream;
/***/ public String objectAtPathDoesNotHaveId;
/***/ public String objectIsCorrupt;
+ /***/ public String objectIsCorrupt3;
/***/ public String objectIsNotA;
/***/ public String objectNotFound;
/***/ public String objectNotFoundIn;
@@ -663,6 +667,7 @@ public class JGitText extends TranslationBundle {
/***/ public String transportProtoSFTP;
/***/ public String transportProtoSSH;
/***/ public String transportProtoTest;
+ /***/ public String transportProvidedRefWithNoObjectId;
/***/ public String transportSSHRetryInterrupt;
/***/ public String treeEntryAlreadyExists;
/***/ public String treeFilterMarkerTooManyFilters;
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 faf27e32bb..784507d88c 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
@@ -44,18 +44,17 @@
package org.eclipse.jgit.internal.storage.dfs;
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC;
+import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC_TXN;
import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
-import static org.eclipse.jgit.lib.RefDatabase.ALL;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Collection;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.internal.JGitText;
@@ -63,13 +62,15 @@ import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
import org.eclipse.jgit.internal.storage.file.PackIndex;
import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.internal.storage.pack.PackWriter;
+import org.eclipse.jgit.internal.storage.reftree.RefTreeNames;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectIdOwnerMap;
+import org.eclipse.jgit.lib.ObjectIdSet;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.storage.pack.PackStatistics;
@@ -78,16 +79,14 @@ import org.eclipse.jgit.util.io.CountingOutputStream;
/** Repack and garbage collect a repository. */
public class DfsGarbageCollector {
private final DfsRepository repo;
-
- private final DfsRefDatabase refdb;
-
+ private final RefDatabase refdb;
private final DfsObjDatabase objdb;
private final List<DfsPackDescription> newPackDesc;
private final List<PackStatistics> newPackStats;
- private final List<PackWriter.ObjectIdSet> newPackObj;
+ private final List<ObjectIdSet> newPackObj;
private DfsReader ctx;
@@ -95,14 +94,11 @@ public class DfsGarbageCollector {
private long coalesceGarbageLimit = 50 << 20;
- private Map<String, Ref> refsBefore;
-
private List<DfsPackFile> packsBefore;
private Set<ObjectId> allHeads;
-
private Set<ObjectId> nonHeads;
-
+ private Set<ObjectId> txnHeads;
private Set<ObjectId> tagTargets;
/**
@@ -117,7 +113,7 @@ public class DfsGarbageCollector {
objdb = repo.getObjectDatabase();
newPackDesc = new ArrayList<DfsPackDescription>(4);
newPackStats = new ArrayList<PackStatistics>(4);
- newPackObj = new ArrayList<PackWriter.ObjectIdSet>(4);
+ newPackObj = new ArrayList<ObjectIdSet>(4);
packConfig = new PackConfig(repo);
packConfig.setIndexVersion(2);
@@ -195,22 +191,25 @@ public class DfsGarbageCollector {
ctx = (DfsReader) objdb.newReader();
try {
- refdb.clearCache();
+ refdb.refresh();
objdb.clearCache();
- refsBefore = refdb.getRefs(ALL);
+ Collection<Ref> refsBefore = RefTreeNames.allRefs(refdb);
packsBefore = packsToRebuild();
if (packsBefore.isEmpty())
return true;
allHeads = new HashSet<ObjectId>();
nonHeads = new HashSet<ObjectId>();
+ txnHeads = new HashSet<ObjectId>();
tagTargets = new HashSet<ObjectId>();
- for (Ref ref : refsBefore.values()) {
+ for (Ref ref : refsBefore) {
if (ref.isSymbolic() || ref.getObjectId() == null)
continue;
if (isHead(ref))
allHeads.add(ref.getObjectId());
+ else if (RefTreeNames.isRefTree(refdb, ref.getName()))
+ txnHeads.add(ref.getObjectId());
else
nonHeads.add(ref.getObjectId());
if (ref.getPeeledObjectId() != null)
@@ -222,6 +221,7 @@ public class DfsGarbageCollector {
try {
packHeads(pm);
packRest(pm);
+ packRefTreeGraph(pm);
packGarbage(pm);
objdb.commitPack(newPackDesc, toPrune());
rollback = false;
@@ -277,18 +277,17 @@ public class DfsGarbageCollector {
try (PackWriter pw = newPackWriter()) {
pw.setTagTargets(tagTargets);
- pw.preparePack(pm, allHeads, Collections.<ObjectId> emptySet());
+ pw.preparePack(pm, allHeads, PackWriter.NONE);
if (0 < pw.getObjectCount())
writePack(GC, pw, pm);
}
}
-
private void packRest(ProgressMonitor pm) throws IOException {
if (nonHeads.isEmpty())
return;
try (PackWriter pw = newPackWriter()) {
- for (PackWriter.ObjectIdSet packedObjs : newPackObj)
+ for (ObjectIdSet packedObjs : newPackObj)
pw.excludeObjects(packedObjs);
pw.preparePack(pm, nonHeads, allHeads);
if (0 < pw.getObjectCount())
@@ -296,6 +295,19 @@ public class DfsGarbageCollector {
}
}
+ private void packRefTreeGraph(ProgressMonitor pm) throws IOException {
+ if (txnHeads.isEmpty())
+ return;
+
+ try (PackWriter pw = newPackWriter()) {
+ for (ObjectIdSet packedObjs : newPackObj)
+ pw.excludeObjects(packedObjs);
+ pw.preparePack(pm, txnHeads, PackWriter.NONE);
+ if (0 < pw.getObjectCount())
+ writePack(GC_TXN, pw, pm);
+ }
+ }
+
private void packGarbage(ProgressMonitor pm) throws IOException {
// TODO(sop) This is ugly. The garbage pack needs to be deleted.
PackConfig cfg = new PackConfig(packConfig);
@@ -328,7 +340,7 @@ public class DfsGarbageCollector {
}
private boolean anyPackHas(AnyObjectId id) {
- for (PackWriter.ObjectIdSet packedObjs : newPackObj)
+ for (ObjectIdSet packedObjs : newPackObj)
if (packedObjs.contains(id))
return true;
return false;
@@ -389,17 +401,10 @@ public class DfsGarbageCollector {
}
}
- final ObjectIdOwnerMap<ObjectIdOwnerMap.Entry> packedObjs = pw
- .getObjectSet();
- newPackObj.add(new PackWriter.ObjectIdSet() {
- public boolean contains(AnyObjectId objectId) {
- return packedObjs.contains(objectId);
- }
- });
-
PackStatistics stats = pw.getStatistics();
pack.setPackStats(stats);
newPackStats.add(stats);
+ newPackObj.add(pw.getObjectSet());
DfsBlockCache.getInstance().getOrCreate(pack, null);
return pack;
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 5f491ff2fd..3641560ee9 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
@@ -91,6 +91,13 @@ public abstract class DfsObjDatabase extends ObjectDatabase {
GC(1),
/**
+ * RefTreeGraph pack was created by Git garbage collection.
+ *
+ * @see DfsGarbageCollector
+ */
+ GC_TXN(1),
+
+ /**
* The pack was created by compacting multiple packs together.
* <p>
* Packs created by compacting multiple packs together aren't nearly as
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 7073763a7a..11aef7feaf 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
@@ -62,6 +62,7 @@ import org.eclipse.jgit.internal.storage.pack.PackWriter;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdSet;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.revwalk.RevObject;
@@ -91,7 +92,7 @@ public class DfsPackCompactor {
private final List<DfsPackFile> srcPacks;
- private final List<PackWriter.ObjectIdSet> exclude;
+ private final List<ObjectIdSet> exclude;
private final List<DfsPackDescription> newPacks;
@@ -113,7 +114,7 @@ public class DfsPackCompactor {
repo = repository;
autoAddSize = 5 * 1024 * 1024; // 5 MiB
srcPacks = new ArrayList<DfsPackFile>();
- exclude = new ArrayList<PackWriter.ObjectIdSet>(4);
+ exclude = new ArrayList<ObjectIdSet>(4);
newPacks = new ArrayList<DfsPackDescription>(1);
newStats = new ArrayList<PackStatistics>(1);
}
@@ -164,7 +165,7 @@ public class DfsPackCompactor {
* objects to not include.
* @return {@code this}.
*/
- public DfsPackCompactor exclude(PackWriter.ObjectIdSet set) {
+ public DfsPackCompactor exclude(ObjectIdSet set) {
exclude.add(set);
return this;
}
@@ -183,11 +184,7 @@ public class DfsPackCompactor {
try (DfsReader ctx = (DfsReader) repo.newObjectReader()) {
idx = pack.getPackIndex(ctx);
}
- return exclude(new PackWriter.ObjectIdSet() {
- public boolean contains(AnyObjectId id) {
- return idx.hasObject(id);
- }
- });
+ return exclude(idx);
}
/**
@@ -343,7 +340,7 @@ public class DfsPackCompactor {
RevObject obj = rw.lookupOrNull(id);
if (obj != null && (obj.has(added) || obj.has(isBase)))
continue;
- for (PackWriter.ObjectIdSet e : exclude)
+ for (ObjectIdSet e : exclude)
if (e.contains(id))
continue SCAN;
want.add(new ObjectIdWithOffset(id, ent.getOffset()));
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 a1035a1284..e5469f6b83 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
@@ -262,6 +262,11 @@ public abstract class DfsRefDatabase extends RefDatabase {
}
@Override
+ public void refresh() {
+ clearCache();
+ }
+
+ @Override
public void close() {
clearCache();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
index 0d5fd0f859..ef8845084b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java
@@ -79,9 +79,6 @@ public abstract class DfsRepository extends Repository {
@Override
public abstract DfsObjDatabase getObjectDatabase();
- @Override
- public abstract DfsRefDatabase getRefDatabase();
-
/** @return a description of this repository. */
public DfsRepositoryDescription getDescription() {
return description;
@@ -95,7 +92,10 @@ public abstract class DfsRepository extends Repository {
* the repository cannot be checked.
*/
public boolean exists() throws IOException {
- return getRefDatabase().exists();
+ if (getRefDatabase() instanceof DfsRefDatabase) {
+ return ((DfsRefDatabase) getRefDatabase()).exists();
+ }
+ return true;
}
@Override
@@ -117,7 +117,7 @@ public abstract class DfsRepository extends Repository {
@Override
public void scanForRepoChanges() throws IOException {
- getRefDatabase().clearCache();
+ getRefDatabase().refresh();
getObjectDatabase().clearCache();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
index 1c664b4097..5e246b47b4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
@@ -16,7 +16,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
-import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.ObjectId;
@@ -24,6 +23,7 @@ import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Ref.Storage;
+import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.SymbolicRef;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTag;
@@ -54,7 +54,7 @@ public class InMemoryRepository extends DfsRepository {
static final AtomicInteger packId = new AtomicInteger();
private final DfsObjDatabase objdb;
- private final DfsRefDatabase refdb;
+ private final RefDatabase refdb;
private boolean performsAtomicTransactions = true;
/**
@@ -80,7 +80,7 @@ public class InMemoryRepository extends DfsRepository {
}
@Override
- public DfsRefDatabase getRefDatabase() {
+ public RefDatabase getRefDatabase() {
return refdb;
}
@@ -310,6 +310,11 @@ public class InMemoryRepository extends DfsRepository {
Map<ObjectId, ObjectId> peeled = new HashMap<>();
try (RevWalk rw = new RevWalk(getRepository())) {
for (ReceiveCommand c : cmds) {
+ if (c.getResult() != ReceiveCommand.Result.NOT_ATTEMPTED) {
+ ReceiveCommand.abort(cmds);
+ return;
+ }
+
if (!ObjectId.zeroId().equals(c.getNewId())) {
try {
RevObject o = rw.parseAny(c.getNewId());
@@ -318,7 +323,7 @@ public class InMemoryRepository extends DfsRepository {
}
} catch (IOException e) {
c.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT);
- reject(cmds);
+ ReceiveCommand.abort(cmds);
return;
}
}
@@ -331,14 +336,17 @@ public class InMemoryRepository extends DfsRepository {
if (r == null) {
if (c.getType() != ReceiveCommand.Type.CREATE) {
c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
- reject(cmds);
+ ReceiveCommand.abort(cmds);
+ return;
+ }
+ } else {
+ ObjectId objectId = r.getObjectId();
+ if (r.isSymbolic() || objectId == null
+ || !objectId.equals(c.getOldId())) {
+ c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
+ ReceiveCommand.abort(cmds);
return;
}
- } else if (r.isSymbolic() || r.getObjectId() == null
- || !r.getObjectId().equals(c.getOldId())) {
- c.setResult(ReceiveCommand.Result.LOCK_FAILURE);
- reject(cmds);
- return;
}
}
@@ -365,15 +373,6 @@ public class InMemoryRepository extends DfsRepository {
clearCache();
}
- private void reject(List<ReceiveCommand> cmds) {
- for (ReceiveCommand c : cmds) {
- if (c.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) {
- c.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON,
- JGitText.get().transactionAborted);
- }
- }
- }
-
@Override
protected boolean compareAndPut(Ref oldRef, Ref newRef)
throws IOException {
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 490cbcaa81..62d2d6969f 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
@@ -63,6 +63,7 @@ import org.eclipse.jgit.events.ConfigChangedEvent;
import org.eclipse.jgit.events.ConfigChangedListener;
import org.eclipse.jgit.events.IndexChangedEvent;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.reftree.RefTreeDatabase;
import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateHandle;
import org.eclipse.jgit.internal.storage.file.ObjectDirectory.AlternateRepository;
import org.eclipse.jgit.lib.BaseRepositoryBuilder;
@@ -201,7 +202,22 @@ public class FileRepository extends Repository {
}
});
- refs = new RefDirectory(this);
+ final long repositoryFormatVersion = getConfig().getLong(
+ ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
+
+ String reftype = repoConfig.getString(
+ "extensions", null, "refsStorage"); //$NON-NLS-1$ //$NON-NLS-2$
+ if (repositoryFormatVersion >= 1 && reftype != null) {
+ if (StringUtils.equalsIgnoreCase(reftype, "reftree")) { //$NON-NLS-1$
+ refs = new RefTreeDatabase(this, new RefDirectory(this));
+ } else {
+ throw new IOException(JGitText.get().unknownRepositoryFormat);
+ }
+ } else {
+ refs = new RefDirectory(this);
+ }
+
objectDatabase = new ObjectDirectory(repoConfig, //
options.getObjectDirectory(), //
options.getAlternateObjectDirectories(), //
@@ -209,10 +225,7 @@ public class FileRepository extends Repository {
new File(getDirectory(), Constants.SHALLOW));
if (objectDatabase.exists()) {
- final long repositoryFormatVersion = getConfig().getLong(
- ConfigConstants.CONFIG_CORE_SECTION, null,
- ConfigConstants.CONFIG_KEY_REPO_FORMAT_VERSION, 0);
- if (repositoryFormatVersion > 0)
+ if (repositoryFormatVersion > 1)
throw new IOException(MessageFormat.format(
JGitText.get().unknownRepositoryFormat2,
Long.valueOf(repositoryFormatVersion)));
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 4c40538b6a..2ce0d47348 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
@@ -45,7 +45,6 @@ package org.eclipse.jgit.internal.storage.file;
import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
-import static org.eclipse.jgit.lib.RefDatabase.ALL;
import java.io.File;
import java.io.FileOutputStream;
@@ -53,6 +52,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
+import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.ArrayList;
@@ -62,14 +62,14 @@ import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
+import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
+import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -78,13 +78,13 @@ import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.pack.PackExt;
import org.eclipse.jgit.internal.storage.pack.PackWriter;
-import org.eclipse.jgit.internal.storage.pack.PackWriter.ObjectIdSet;
-import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.internal.storage.reftree.RefTreeNames;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdSet;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Ref.Storage;
@@ -127,7 +127,7 @@ public class GC {
* difference between the current refs and the refs which existed during
* last {@link #repack()}.
*/
- private Map<String, Ref> lastPackedRefs;
+ private Collection<Ref> lastPackedRefs;
/**
* Holds the starting time of the last repack() execution. This is needed in
@@ -361,17 +361,20 @@ public class GC {
// during last repack(). Only those refs will survive which have been
// added or modified since the last repack. Only these can save existing
// loose refs from being pruned.
- Map<String, Ref> newRefs;
+ Collection<Ref> newRefs;
if (lastPackedRefs == null || lastPackedRefs.isEmpty())
newRefs = getAllRefs();
else {
- newRefs = new HashMap<String, Ref>();
- for (Iterator<Map.Entry<String, Ref>> i = getAllRefs().entrySet()
- .iterator(); i.hasNext();) {
- Entry<String, Ref> newEntry = i.next();
- Ref old = lastPackedRefs.get(newEntry.getKey());
- if (!equals(newEntry.getValue(), old))
- newRefs.put(newEntry.getKey(), newEntry.getValue());
+ Map<String, Ref> last = new HashMap<>();
+ for (Ref r : lastPackedRefs) {
+ last.put(r.getName(), r);
+ }
+ newRefs = new ArrayList<>();
+ for (Ref r : getAllRefs()) {
+ Ref old = last.get(r.getName());
+ if (!equals(r, old)) {
+ newRefs.add(r);
+ }
}
}
@@ -383,10 +386,10 @@ public class GC {
// leave this method.
ObjectWalk w = new ObjectWalk(repo);
try {
- for (Ref cr : newRefs.values())
+ for (Ref cr : newRefs)
w.markStart(w.parseAny(cr.getObjectId()));
if (lastPackedRefs != null)
- for (Ref lpr : lastPackedRefs.values())
+ for (Ref lpr : lastPackedRefs)
w.markUninteresting(w.parseAny(lpr.getObjectId()));
removeReferenced(deletionCandidates, w);
} finally {
@@ -404,11 +407,11 @@ public class GC {
// additional reflog entries not handled during last repack()
ObjectWalk w = new ObjectWalk(repo);
try {
- for (Ref ar : getAllRefs().values())
+ for (Ref ar : getAllRefs())
for (ObjectId id : listRefLogObjects(ar, lastRepackTime))
w.markStart(w.parseAny(id));
if (lastPackedRefs != null)
- for (Ref lpr : lastPackedRefs.values())
+ for (Ref lpr : lastPackedRefs)
w.markUninteresting(w.parseAny(lpr.getObjectId()));
removeReferenced(deletionCandidates, w);
} finally {
@@ -483,9 +486,10 @@ public class GC {
return false;
return r1.getTarget().getName().equals(r2.getTarget().getName());
} else {
- if (r2.isSymbolic())
+ if (r2.isSymbolic()) {
return false;
- return r1.getObjectId().equals(r2.getObjectId());
+ }
+ return Objects.equals(r1.getObjectId(), r2.getObjectId());
}
}
@@ -528,19 +532,23 @@ public class GC {
Collection<PackFile> toBeDeleted = repo.getObjectDatabase().getPacks();
long time = System.currentTimeMillis();
- Map<String, Ref> refsBefore = getAllRefs();
+ Collection<Ref> refsBefore = getAllRefs();
Set<ObjectId> allHeads = new HashSet<ObjectId>();
Set<ObjectId> nonHeads = new HashSet<ObjectId>();
+ Set<ObjectId> txnHeads = new HashSet<ObjectId>();
Set<ObjectId> tagTargets = new HashSet<ObjectId>();
Set<ObjectId> indexObjects = listNonHEADIndexObjects();
+ RefDatabase refdb = repo.getRefDatabase();
- for (Ref ref : refsBefore.values()) {
+ for (Ref ref : refsBefore) {
nonHeads.addAll(listRefLogObjects(ref, 0));
if (ref.isSymbolic() || ref.getObjectId() == null)
continue;
if (ref.getName().startsWith(Constants.R_HEADS))
allHeads.add(ref.getObjectId());
+ else if (RefTreeNames.isRefTree(refdb, ref.getName()))
+ txnHeads.add(ref.getObjectId());
else
nonHeads.add(ref.getObjectId());
if (ref.getPeeledObjectId() != null)
@@ -550,7 +558,7 @@ public class GC {
List<ObjectIdSet> excluded = new LinkedList<ObjectIdSet>();
for (final PackFile f : repo.getObjectDatabase().getPacks())
if (f.shouldBeKept())
- excluded.add(objectIdSet(f.getIndex()));
+ excluded.add(f.getIndex());
tagTargets.addAll(allHeads);
nonHeads.addAll(indexObjects);
@@ -562,7 +570,7 @@ public class GC {
tagTargets, excluded);
if (heads != null) {
ret.add(heads);
- excluded.add(0, objectIdSet(heads.getIndex()));
+ excluded.add(0, heads.getIndex());
}
}
if (!nonHeads.isEmpty()) {
@@ -570,6 +578,11 @@ public class GC {
if (rest != null)
ret.add(rest);
}
+ if (!txnHeads.isEmpty()) {
+ PackFile txn = writePack(txnHeads, PackWriter.NONE, null, excluded);
+ if (txn != null)
+ ret.add(txn);
+ }
try {
deleteOldPacks(toBeDeleted, ret);
} catch (ParseException e) {
@@ -622,11 +635,16 @@ public class GC {
* @return a map where names of refs point to ref objects
* @throws IOException
*/
- private Map<String, Ref> getAllRefs() throws IOException {
- Map<String, Ref> ret = repo.getRefDatabase().getRefs(ALL);
- for (Ref ref : repo.getRefDatabase().getAdditionalRefs())
- ret.put(ref.getName(), ref);
- return ret;
+ private Collection<Ref> getAllRefs() throws IOException {
+ Collection<Ref> refs = RefTreeNames.allRefs(repo.getRefDatabase());
+ List<Ref> addl = repo.getRefDatabase().getAdditionalRefs();
+ if (!addl.isEmpty()) {
+ List<Ref> all = new ArrayList<>(refs.size() + addl.size());
+ all.addAll(refs);
+ all.addAll(addl);
+ return all;
+ }
+ return refs;
}
/**
@@ -681,8 +699,8 @@ public class GC {
}
}
- private PackFile writePack(Set<? extends ObjectId> want,
- Set<? extends ObjectId> have, Set<ObjectId> tagTargets,
+ private PackFile writePack(@NonNull Set<? extends ObjectId> want,
+ @NonNull Set<? extends ObjectId> have, Set<ObjectId> tagTargets,
List<ObjectIdSet> excludeObjects) throws IOException {
File tmpPack = null;
Map<PackExt, File> tmpExts = new TreeMap<PackExt, File>(
@@ -788,39 +806,33 @@ public class GC {
break;
}
tmpPack.setReadOnly();
- boolean delete = true;
- try {
- FileUtils.rename(tmpPack, realPack);
- delete = false;
- for (Map.Entry<PackExt, File> tmpEntry : tmpExts.entrySet()) {
- File tmpExt = tmpEntry.getValue();
- tmpExt.setReadOnly();
-
- File realExt = nameFor(
- id, "." + tmpEntry.getKey().getExtension()); //$NON-NLS-1$
- try {
- FileUtils.rename(tmpExt, realExt);
- } catch (IOException e) {
- File newExt = new File(realExt.getParentFile(),
- realExt.getName() + ".new"); //$NON-NLS-1$
- if (!tmpExt.renameTo(newExt))
- newExt = tmpExt;
- throw new IOException(MessageFormat.format(
- JGitText.get().panicCantRenameIndexFile, newExt,
- realExt));
- }
- }
- } finally {
- if (delete) {
- if (tmpPack.exists())
- tmpPack.delete();
- for (File tmpExt : tmpExts.values()) {
- if (tmpExt.exists())
- tmpExt.delete();
+ FileUtils.rename(tmpPack, realPack, StandardCopyOption.ATOMIC_MOVE);
+ for (Map.Entry<PackExt, File> tmpEntry : tmpExts.entrySet()) {
+ File tmpExt = tmpEntry.getValue();
+ tmpExt.setReadOnly();
+
+ File realExt = nameFor(id,
+ "." + tmpEntry.getKey().getExtension()); //$NON-NLS-1$
+ try {
+ FileUtils.rename(tmpExt, realExt,
+ StandardCopyOption.ATOMIC_MOVE);
+ } catch (IOException e) {
+ File newExt = new File(realExt.getParentFile(),
+ realExt.getName() + ".new"); //$NON-NLS-1$
+ try {
+ FileUtils.rename(tmpExt, newExt,
+ StandardCopyOption.ATOMIC_MOVE);
+ } catch (IOException e2) {
+ newExt = tmpExt;
+ e = e2;
}
+ throw new IOException(MessageFormat.format(
+ JGitText.get().panicCantRenameIndexFile, newExt,
+ realExt), e);
}
}
+
return repo.getObjectDatabase().openPack(realPack);
} finally {
if (tmpPack != null && tmpPack.exists())
@@ -998,12 +1010,4 @@ public class GC {
this.expire = expire;
expireAgeMillis = -1;
}
-
- private static ObjectIdSet objectIdSet(final PackIndex idx) {
- return new ObjectIdSet() {
- public boolean contains(AnyObjectId objectId) {
- return idx.hasObject(objectId);
- }
- };
- }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java
new file mode 100644
index 0000000000..1e2617c0e3
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LazyObjectIdSetFile.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015, 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.file;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.MutableObjectId;
+import org.eclipse.jgit.lib.ObjectIdOwnerMap;
+import org.eclipse.jgit.lib.ObjectIdSet;
+
+/** Lazily loads a set of ObjectIds, one per line. */
+public class LazyObjectIdSetFile implements ObjectIdSet {
+ private final File src;
+ private ObjectIdOwnerMap<Entry> set;
+
+ /**
+ * Create a new lazy set from a file.
+ *
+ * @param src
+ * the source file.
+ */
+ public LazyObjectIdSetFile(File src) {
+ this.src = src;
+ }
+
+ @Override
+ public boolean contains(AnyObjectId objectId) {
+ if (set == null) {
+ set = load();
+ }
+ return set.contains(objectId);
+ }
+
+ private ObjectIdOwnerMap<Entry> load() {
+ ObjectIdOwnerMap<Entry> r = new ObjectIdOwnerMap<>();
+ try (FileInputStream fin = new FileInputStream(src);
+ Reader rin = new InputStreamReader(fin, UTF_8);
+ BufferedReader br = new BufferedReader(rin)) {
+ MutableObjectId id = new MutableObjectId();
+ for (String line; (line = br.readLine()) != null;) {
+ id.fromString(line);
+ if (!r.contains(id)) {
+ r.add(new Entry(id));
+ }
+ }
+ } catch (IOException e) {
+ // Ignore IO errors accessing the lazy set.
+ }
+ return r;
+ }
+
+ static class Entry extends ObjectIdOwnerMap.Entry {
+ Entry(AnyObjectId id) {
+ super(id);
+ }
+ }
+}
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 e23ca741b8..ce9677a62d 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
@@ -54,6 +54,7 @@ import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
+import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
import org.eclipse.jgit.errors.LockFailedException;
@@ -128,8 +129,6 @@ public class LockFile {
private FileSnapshot commitSnapshot;
- private final FS fs;
-
/**
* Create a new lock for any file.
*
@@ -138,11 +137,24 @@ public class LockFile {
* @param fs
* the file system abstraction which will be necessary to perform
* certain file system operations.
+ * @deprecated use {@link LockFile#LockFile(File)} instead
*/
+ @Deprecated
public LockFile(final File f, final FS fs) {
ref = f;
lck = getLockFile(ref);
- this.fs = fs;
+ }
+
+ /**
+ * Create a new lock for any file.
+ *
+ * @param f
+ * the file that will be locked.
+ * @since 4.2
+ */
+ public LockFile(final File f) {
+ ref = f;
+ lck = getLockFile(ref);
}
/**
@@ -441,56 +453,14 @@ public class LockFile {
}
saveStatInformation();
- if (lck.renameTo(ref)) {
+ try {
+ FileUtils.rename(lck, ref, StandardCopyOption.ATOMIC_MOVE);
haveLck = false;
return true;
+ } catch (IOException e) {
+ unlock();
+ return false;
}
- if (!ref.exists() || deleteRef()) {
- if (renameLock()) {
- haveLck = false;
- return true;
- }
- }
- unlock();
- return false;
- }
-
- private boolean deleteRef() {
- if (!fs.retryFailedLockFileCommit())
- return ref.delete();
-
- // File deletion fails on windows if another thread is
- // concurrently reading the same file. So try a few times.
- //
- for (int attempts = 0; attempts < 10; attempts++) {
- if (ref.delete())
- return true;
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- return false;
- }
- }
- return false;
- }
-
- private boolean renameLock() {
- if (!fs.retryFailedLockFileCommit())
- return lck.renameTo(ref);
-
- // File renaming fails on windows if another thread is
- // concurrently reading the same file. So try a few times.
- //
- for (int attempts = 0; attempts < 10; attempts++) {
- if (lck.renameTo(ref))
- return true;
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- return false;
- }
- }
- return false;
}
private void saveStatInformation() {
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 bd1d488d94..ea80528518 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
@@ -52,6 +52,9 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
+import java.nio.file.AtomicMoveNotSupportedException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -608,10 +611,16 @@ public class ObjectDirectory extends FileObjectDatabase {
FileUtils.delete(tmp, FileUtils.RETRY);
return InsertLooseObjectResult.EXISTS_LOOSE;
}
- if (tmp.renameTo(dst)) {
+ try {
+ Files.move(tmp.toPath(), dst.toPath(),
+ StandardCopyOption.ATOMIC_MOVE);
dst.setReadOnly();
unpackedObjectCache.add(id);
return InsertLooseObjectResult.INSERTED;
+ } catch (AtomicMoveNotSupportedException e) {
+ LOG.error(e.getMessage(), e);
+ } catch (IOException e) {
+ // ignore
}
// Maybe the directory doesn't exist yet as the object
@@ -619,10 +628,16 @@ public class ObjectDirectory extends FileObjectDatabase {
// try the rename first as the directory likely does exist.
//
FileUtils.mkdir(dst.getParentFile(), true);
- if (tmp.renameTo(dst)) {
+ try {
+ Files.move(tmp.toPath(), dst.toPath(),
+ StandardCopyOption.ATOMIC_MOVE);
dst.setReadOnly();
unpackedObjectCache.add(id);
return InsertLooseObjectResult.INSERTED;
+ } catch (AtomicMoveNotSupportedException e) {
+ LOG.error(e.getMessage(), e);
+ } catch (IOException e) {
+ LOG.debug(e.getMessage(), e);
}
if (!createDuplicate && has(id)) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
index 1c076ee099..2e6c245ea1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
@@ -50,6 +50,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
+import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.text.MessageFormat;
import java.util.Arrays;
@@ -476,20 +477,25 @@ public class ObjectDirectoryPackParser extends PackParser {
}
}
- if (!tmpPack.renameTo(finalPack)) {
+ try {
+ FileUtils.rename(tmpPack, finalPack,
+ StandardCopyOption.ATOMIC_MOVE);
+ } catch (IOException e) {
cleanupTemporaryFiles();
keep.unlock();
throw new IOException(MessageFormat.format(
- JGitText.get().cannotMovePackTo, finalPack));
+ JGitText.get().cannotMovePackTo, finalPack), e);
}
- if (!tmpIdx.renameTo(finalIdx)) {
+ try {
+ FileUtils.rename(tmpIdx, finalIdx, StandardCopyOption.ATOMIC_MOVE);
+ } catch (IOException e) {
cleanupTemporaryFiles();
keep.unlock();
if (!finalPack.delete())
finalPack.deleteOnExit();
throw new IOException(MessageFormat.format(
- JGitText.get().cannotMoveIndexTo, finalIdx));
+ JGitText.get().cannotMoveIndexTo, finalIdx), e);
}
try {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java
index 0040aea713..f36bd4d70c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java
@@ -60,6 +60,7 @@ import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdSet;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.NB;
@@ -72,7 +73,8 @@ import org.eclipse.jgit.util.NB;
* by ObjectId.
* </p>
*/
-public abstract class PackIndex implements Iterable<PackIndex.MutableEntry> {
+public abstract class PackIndex
+ implements Iterable<PackIndex.MutableEntry>, ObjectIdSet {
/**
* Open an existing pack <code>.idx</code> file for reading.
* <p>
@@ -166,6 +168,11 @@ public abstract class PackIndex implements Iterable<PackIndex.MutableEntry> {
return findOffset(id) != -1;
}
+ @Override
+ public boolean contains(AnyObjectId id) {
+ return findOffset(id) != -1;
+ }
+
/**
* Provide iterator that gives access to index entries. Note, that iterator
* returns reference to mutable object, the same reference in each call -
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 69f7e97071..2c8e5f9d11 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
@@ -73,6 +73,7 @@ import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.errors.InvalidObjectIdException;
import org.eclipse.jgit.errors.LockFailedException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -715,16 +716,20 @@ public class RefDirectory extends RefDatabase {
*/
private Ref peeledPackedRef(Ref f)
throws MissingObjectException, IOException {
- if (f.getStorage().isPacked() && f.isPeeled())
+ if (f.getStorage().isPacked() && f.isPeeled()) {
return f;
- if (!f.isPeeled())
+ }
+ if (!f.isPeeled()) {
f = peel(f);
- if (f.getPeeledObjectId() != null)
+ }
+ ObjectId peeledObjectId = f.getPeeledObjectId();
+ if (peeledObjectId != null) {
return new ObjectIdRef.PeeledTag(PACKED, f.getName(),
- f.getObjectId(), f.getPeeledObjectId());
- else
+ f.getObjectId(), peeledObjectId);
+ } else {
return new ObjectIdRef.PeeledNonTag(PACKED, f.getName(),
f.getObjectId());
+ }
}
void log(final RefUpdate update, final String msg, final boolean deref)
@@ -985,7 +990,7 @@ public class RefDirectory extends RefDatabase {
try {
id = ObjectId.fromString(buf, 0);
if (ref != null && !ref.isSymbolic()
- && ref.getTarget().getObjectId().equals(id)) {
+ && id.equals(ref.getTarget().getObjectId())) {
assert(currentSnapshot != null);
currentSnapshot.setClean(otherSnapshot);
return ref;
@@ -1103,8 +1108,8 @@ public class RefDirectory extends RefDatabase {
implements LooseRef {
private final FileSnapshot snapShot;
- LoosePeeledTag(FileSnapshot snapshot, String refName, ObjectId id,
- ObjectId p) {
+ LoosePeeledTag(FileSnapshot snapshot, @NonNull String refName,
+ @NonNull ObjectId id, @NonNull ObjectId p) {
super(LOOSE, refName, id, p);
this.snapShot = snapshot;
}
@@ -1122,7 +1127,8 @@ public class RefDirectory extends RefDatabase {
implements LooseRef {
private final FileSnapshot snapShot;
- LooseNonTag(FileSnapshot snapshot, String refName, ObjectId id) {
+ LooseNonTag(FileSnapshot snapshot, @NonNull String refName,
+ @NonNull ObjectId id) {
super(LOOSE, refName, id);
this.snapShot = snapshot;
}
@@ -1140,7 +1146,8 @@ public class RefDirectory extends RefDatabase {
implements LooseRef {
private FileSnapshot snapShot;
- LooseUnpeeled(FileSnapshot snapShot, String refName, ObjectId id) {
+ LooseUnpeeled(FileSnapshot snapShot, @NonNull String refName,
+ @NonNull ObjectId id) {
super(LOOSE, refName, id);
this.snapShot = snapShot;
}
@@ -1149,13 +1156,24 @@ public class RefDirectory extends RefDatabase {
return snapShot;
}
+ @NonNull
+ @Override
+ public ObjectId getObjectId() {
+ ObjectId id = super.getObjectId();
+ assert id != null; // checked in constructor
+ return id;
+ }
+
public LooseRef peel(ObjectIdRef newLeaf) {
- if (newLeaf.getPeeledObjectId() != null)
+ ObjectId peeledObjectId = newLeaf.getPeeledObjectId();
+ ObjectId objectId = getObjectId();
+ if (peeledObjectId != null) {
return new LoosePeeledTag(snapShot, getName(),
- getObjectId(), newLeaf.getPeeledObjectId());
- else
+ objectId, peeledObjectId);
+ } else {
return new LooseNonTag(snapShot, getName(),
- getObjectId());
+ objectId);
+ }
}
}
@@ -1163,7 +1181,8 @@ public class RefDirectory extends RefDatabase {
LooseRef {
private final FileSnapshot snapShot;
- LooseSymbolicRef(FileSnapshot snapshot, String refName, Ref target) {
+ LooseSymbolicRef(FileSnapshot snapshot, @NonNull String refName,
+ @NonNull Ref target) {
super(refName, target);
this.snapShot = snapshot;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java
index ba4a63d7fe..4b803a5144 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectoryRename.java
@@ -46,6 +46,8 @@ package org.eclipse.jgit.internal.storage.file;
import java.io.File;
import java.io.IOException;
+import java.nio.file.AtomicMoveNotSupportedException;
+import java.nio.file.StandardCopyOption;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
@@ -54,6 +56,8 @@ import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Rename any reference stored by {@link RefDirectory}.
@@ -66,6 +70,9 @@ import org.eclipse.jgit.util.FileUtils;
* directory that happens to match the source name.
*/
class RefDirectoryRename extends RefRename {
+ private static final Logger LOG = LoggerFactory
+ .getLogger(RefDirectoryRename.class);
+
private final RefDirectory refdb;
/**
@@ -201,13 +208,25 @@ class RefDirectoryRename extends RefRename {
}
private static boolean rename(File src, File dst) {
- if (src.renameTo(dst))
+ try {
+ FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE);
return true;
+ } catch (AtomicMoveNotSupportedException e) {
+ LOG.error(e.getMessage(), e);
+ } catch (IOException e) {
+ // ignore
+ }
File dir = dst.getParentFile();
if ((dir.exists() || !dir.mkdirs()) && !dir.isDirectory())
return false;
- return src.renameTo(dst);
+ try {
+ FileUtils.rename(src, dst, StandardCopyOption.ATOMIC_MOVE);
+ return true;
+ } catch (IOException e) {
+ LOG.error(e.getMessage(), e);
+ return false;
+ }
}
private boolean linkHEAD(RefUpdate target) {
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 19b6b080da..525f9aecc7 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
@@ -80,6 +80,7 @@ import java.util.zip.CheckedOutputStream;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
+import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.LargeObjectException;
@@ -99,6 +100,7 @@ import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdOwnerMap;
+import org.eclipse.jgit.lib.ObjectIdSet;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.ProgressMonitor;
@@ -161,17 +163,8 @@ import org.eclipse.jgit.util.TemporaryBuffer;
public class PackWriter implements AutoCloseable {
private static final int PACK_VERSION_GENERATED = 2;
- /** A collection of object ids. */
- public interface ObjectIdSet {
- /**
- * Returns true if the objectId is contained within the collection.
- *
- * @param objectId
- * the objectId to find
- * @return whether the collection contains the objectId.
- */
- boolean contains(AnyObjectId objectId);
- }
+ /** Empty set of objects for {@code preparePack()}. */
+ public static Set<ObjectId> NONE = Collections.emptySet();
private static final Map<WeakReference<PackWriter>, Boolean> instances =
new ConcurrentHashMap<WeakReference<PackWriter>, Boolean>();
@@ -681,7 +674,7 @@ public class PackWriter implements AutoCloseable {
* @throws IOException
* when some I/O problem occur during reading objects.
*/
- public void preparePack(final Iterator<RevObject> objectsSource)
+ public void preparePack(@NonNull Iterator<RevObject> objectsSource)
throws IOException {
while (objectsSource.hasNext()) {
addObject(objectsSource.next());
@@ -704,16 +697,18 @@ public class PackWriter implements AutoCloseable {
* progress during object enumeration.
* @param want
* collection of objects to be marked as interesting (start
- * points of graph traversal).
+ * points of graph traversal). Must not be {@code null}.
* @param have
* collection of objects to be marked as uninteresting (end
- * points of graph traversal).
+ * points of graph traversal). Pass {@link #NONE} if all objects
+ * reachable from {@code want} are desired, such as when serving
+ * a clone.
* @throws IOException
* when some I/O problem occur during reading objects.
*/
public void preparePack(ProgressMonitor countingMonitor,
- Set<? extends ObjectId> want,
- Set<? extends ObjectId> have) throws IOException {
+ @NonNull Set<? extends ObjectId> want,
+ @NonNull Set<? extends ObjectId> have) throws IOException {
ObjectWalk ow;
if (shallowPack)
ow = new DepthWalk.ObjectWalk(reader, depth);
@@ -740,17 +735,19 @@ public class PackWriter implements AutoCloseable {
* ObjectWalk to perform enumeration.
* @param interestingObjects
* collection of objects to be marked as interesting (start
- * points of graph traversal).
+ * points of graph traversal). Must not be {@code null}.
* @param uninterestingObjects
* collection of objects to be marked as uninteresting (end
- * points of graph traversal).
+ * points of graph traversal). Pass {@link #NONE} if all objects
+ * reachable from {@code want} are desired, such as when serving
+ * a clone.
* @throws IOException
* when some I/O problem occur during reading objects.
*/
public void preparePack(ProgressMonitor countingMonitor,
- ObjectWalk walk,
- final Set<? extends ObjectId> interestingObjects,
- final Set<? extends ObjectId> uninterestingObjects)
+ @NonNull ObjectWalk walk,
+ @NonNull Set<? extends ObjectId> interestingObjects,
+ @NonNull Set<? extends ObjectId> uninterestingObjects)
throws IOException {
if (countingMonitor == null)
countingMonitor = NullProgressMonitor.INSTANCE;
@@ -1551,6 +1548,8 @@ public class PackWriter implements AutoCloseable {
if (zbuf != null) {
out.writeHeader(otp, otp.getCachedSize());
out.write(zbuf);
+ typeStats.cntDeltas++;
+ typeStats.deltaBytes += out.length() - otp.getOffset();
return;
}
}
@@ -1606,17 +1605,12 @@ public class PackWriter implements AutoCloseable {
out.write(packcsum);
}
- private void findObjectsToPack(final ProgressMonitor countingMonitor,
- final ObjectWalk walker, final Set<? extends ObjectId> want,
- Set<? extends ObjectId> have)
- throws MissingObjectException, IOException,
- IncorrectObjectTypeException {
+ private void findObjectsToPack(@NonNull ProgressMonitor countingMonitor,
+ @NonNull ObjectWalk walker, @NonNull Set<? extends ObjectId> want,
+ @NonNull Set<? extends ObjectId> have) throws IOException {
final long countingStart = System.currentTimeMillis();
beginPhase(PackingPhase.COUNTING, countingMonitor, ProgressMonitor.UNKNOWN);
- if (have == null)
- have = Collections.emptySet();
-
stats.interestingObjects = Collections.unmodifiableSet(new HashSet<ObjectId>(want));
stats.uninterestingObjects = Collections.unmodifiableSet(new HashSet<ObjectId>(have));
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/AlwaysFailUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/AlwaysFailUpdate.java
new file mode 100644
index 0000000000..12ef8734c4
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/AlwaysFailUpdate.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016, 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.reftree;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
+
+/** Update that always rejects with {@code LOCK_FAILURE}. */
+class AlwaysFailUpdate extends RefUpdate {
+ private final RefTreeDatabase refdb;
+
+ AlwaysFailUpdate(RefTreeDatabase refdb, String name) {
+ super(new ObjectIdRef.Unpeeled(Ref.Storage.NEW, name, null));
+ this.refdb = refdb;
+ setCheckConflicting(false);
+ }
+
+ @Override
+ protected RefDatabase getRefDatabase() {
+ return refdb;
+ }
+
+ @Override
+ protected Repository getRepository() {
+ return refdb.getRepository();
+ }
+
+ @Override
+ protected boolean tryLock(boolean deref) throws IOException {
+ return false;
+ }
+
+ @Override
+ protected void unlock() {
+ // No locks are held here.
+ }
+
+ @Override
+ protected Result doUpdate(Result desiredResult) {
+ return Result.LOCK_FAILURE;
+ }
+
+ @Override
+ protected Result doDelete(Result desiredResult) {
+ return Result.LOCK_FAILURE;
+ }
+
+ @Override
+ protected Result doLink(String target) {
+ return Result.LOCK_FAILURE;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java
new file mode 100644
index 0000000000..dd08375f21
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Command.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2016, 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.reftree;
+
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.encode;
+import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK;
+import static org.eclipse.jgit.lib.Ref.Storage.NETWORK;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.Ref;
+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.transport.ReceiveCommand.Result;
+
+/**
+ * Command to create, update or delete an entry inside a {@link RefTree}.
+ * <p>
+ * Unlike {@link ReceiveCommand} (which can only update a reference to an
+ * {@link ObjectId}), a RefTree Command can also create, modify or delete
+ * symbolic references to a target reference.
+ * <p>
+ * RefTree Commands may wrap a {@code ReceiveCommand} to allow callers to
+ * process an existing ReceiveCommand against a RefTree.
+ * <p>
+ * Commands should be passed into {@link RefTree#apply(java.util.Collection)}
+ * for processing.
+ */
+public class Command {
+ /**
+ * Set unprocessed commands as failed due to transaction aborted.
+ * <p>
+ * If a command is still {@link Result#NOT_ATTEMPTED} it will be set to
+ * {@link Result#REJECTED_OTHER_REASON}. If {@code why} is non-null its
+ * contents will be used as the message for the first command status.
+ *
+ * @param commands
+ * commands to mark as failed.
+ * @param why
+ * optional message to set on the first aborted command.
+ */
+ public static void abort(Iterable<Command> commands, @Nullable String why) {
+ if (why == null || why.isEmpty()) {
+ why = JGitText.get().transactionAborted;
+ }
+ for (Command c : commands) {
+ if (c.getResult() == NOT_ATTEMPTED) {
+ c.setResult(REJECTED_OTHER_REASON, why);
+ why = JGitText.get().transactionAborted;
+ }
+ }
+ }
+
+ private final Ref oldRef;
+ private final Ref newRef;
+ private final ReceiveCommand cmd;
+ private Result result;
+
+ /**
+ * Create a command to create, update or delete a reference.
+ * <p>
+ * At least one of {@code oldRef} or {@code newRef} must be supplied.
+ *
+ * @param oldRef
+ * expected value. Null if the ref should not exist.
+ * @param newRef
+ * desired value, must be peeled if not null and not symbolic.
+ * Null to delete the ref.
+ */
+ public Command(@Nullable Ref oldRef, @Nullable Ref newRef) {
+ this.oldRef = oldRef;
+ this.newRef = newRef;
+ this.cmd = null;
+ this.result = NOT_ATTEMPTED;
+
+ if (oldRef == null && newRef == null) {
+ throw new IllegalArgumentException();
+ }
+ if (newRef != null && !newRef.isPeeled() && !newRef.isSymbolic()) {
+ throw new IllegalArgumentException();
+ }
+ if (oldRef != null && newRef != null
+ && !oldRef.getName().equals(newRef.getName())) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Construct a RefTree command wrapped around a ReceiveCommand.
+ *
+ * @param rw
+ * walk instance to peel the {@code newId}.
+ * @param cmd
+ * command received from a push client.
+ * @throws MissingObjectException
+ * {@code oldId} or {@code newId} is missing.
+ * @throws IOException
+ * {@code oldId} or {@code newId} cannot be peeled.
+ */
+ public Command(RevWalk rw, ReceiveCommand cmd)
+ throws MissingObjectException, IOException {
+ this.oldRef = toRef(rw, cmd.getOldId(), cmd.getRefName(), false);
+ this.newRef = toRef(rw, cmd.getNewId(), cmd.getRefName(), true);
+ this.cmd = cmd;
+ }
+
+ static Ref toRef(RevWalk rw, ObjectId id, String name,
+ boolean mustExist) throws MissingObjectException, IOException {
+ if (ObjectId.zeroId().equals(id)) {
+ return null;
+ }
+
+ try {
+ RevObject o = rw.parseAny(id);
+ if (o instanceof RevTag) {
+ RevObject p = rw.peel(o);
+ return new ObjectIdRef.PeeledTag(NETWORK, name, id, p.copy());
+ }
+ return new ObjectIdRef.PeeledNonTag(NETWORK, name, id);
+ } catch (MissingObjectException e) {
+ if (mustExist) {
+ throw e;
+ }
+ return new ObjectIdRef.Unpeeled(NETWORK, name, id);
+ }
+ }
+
+ /** @return name of the reference affected by this command. */
+ public String getRefName() {
+ if (cmd != null) {
+ return cmd.getRefName();
+ } else if (newRef != null) {
+ return newRef.getName();
+ }
+ return oldRef.getName();
+ }
+
+ /**
+ * Set the result of this command.
+ *
+ * @param result
+ * the command result.
+ */
+ public void setResult(Result result) {
+ setResult(result, null);
+ }
+
+ /**
+ * Set the result of this command.
+ *
+ * @param result
+ * the command result.
+ * @param why
+ * optional message explaining the result status.
+ */
+ public void setResult(Result result, @Nullable String why) {
+ if (cmd != null) {
+ cmd.setResult(result, why);
+ } else {
+ this.result = result;
+ }
+ }
+
+ /** @return result of executing this command. */
+ public Result getResult() {
+ return cmd != null ? cmd.getResult() : result;
+ }
+
+ /** @return optional message explaining command failure. */
+ @Nullable
+ public String getMessage() {
+ return cmd != null ? cmd.getMessage() : null;
+ }
+
+ /**
+ * Old peeled reference.
+ *
+ * @return the old reference; null if the command is creating the reference.
+ */
+ @Nullable
+ public Ref getOldRef() {
+ return oldRef;
+ }
+
+ /**
+ * New peeled reference.
+ *
+ * @return the new reference; null if the command is deleting the reference.
+ */
+ @Nullable
+ public Ref getNewRef() {
+ return newRef;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder s = new StringBuilder();
+ append(s, oldRef, "CREATE"); //$NON-NLS-1$
+ s.append(' ');
+ append(s, newRef, "DELETE"); //$NON-NLS-1$
+ s.append(' ').append(getRefName());
+ s.append(' ').append(getResult());
+ if (getMessage() != null) {
+ s.append(' ').append(getMessage());
+ }
+ return s.toString();
+ }
+
+ private static void append(StringBuilder s, Ref r, String nullName) {
+ if (r == null) {
+ s.append(nullName);
+ } else if (r.isSymbolic()) {
+ s.append(r.getTarget().getName());
+ } else {
+ ObjectId id = r.getObjectId();
+ if (id != null) {
+ s.append(id.name());
+ }
+ }
+ }
+
+ /**
+ * Check the entry is consistent with either the old or the new ref.
+ *
+ * @param entry
+ * current entry; null if the entry does not exist.
+ * @return true if entry matches {@link #getOldRef()} or
+ * {@link #getNewRef()}; otherwise false.
+ */
+ boolean checkRef(@Nullable DirCacheEntry entry) {
+ if (entry != null && entry.getRawMode() == 0) {
+ entry = null;
+ }
+ return check(entry, oldRef) || check(entry, newRef);
+ }
+
+ private static boolean check(@Nullable DirCacheEntry cur,
+ @Nullable Ref exp) {
+ if (cur == null) {
+ // Does not exist, ok if oldRef does not exist.
+ return exp == null;
+ } else if (exp == null) {
+ // Expected to not exist, but currently exists, fail.
+ return false;
+ }
+
+ if (exp.isSymbolic()) {
+ String dst = exp.getTarget().getName();
+ return cur.getRawMode() == TYPE_SYMLINK
+ && cur.getObjectId().equals(symref(dst));
+ }
+
+ return cur.getRawMode() == TYPE_GITLINK
+ && cur.getObjectId().equals(exp.getObjectId());
+ }
+
+ static ObjectId symref(String s) {
+ @SuppressWarnings("resource")
+ ObjectInserter.Formatter fmt = new ObjectInserter.Formatter();
+ return fmt.idFor(OBJ_BLOB, encode(s));
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java
new file mode 100644
index 0000000000..85690c8ca5
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTree.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2016, 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.reftree;
+
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.R_REFS;
+import static org.eclipse.jgit.lib.Constants.encode;
+import static org.eclipse.jgit.lib.FileMode.GITLINK;
+import static org.eclipse.jgit.lib.FileMode.SYMLINK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+import static org.eclipse.jgit.lib.RefDatabase.MAX_SYMBOLIC_REF_DEPTH;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEditor;
+import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
+import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.DirCacheNameConflictException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * Tree of references in the reference graph.
+ * <p>
+ * The root corresponds to the {@code "refs/"} subdirectory, for example the
+ * default reference {@code "refs/heads/master"} is stored at path
+ * {@code "heads/master"} in a {@code RefTree}.
+ * <p>
+ * Normal references are stored as {@link FileMode#GITLINK} tree entries. The
+ * ObjectId in the tree entry is the ObjectId the reference refers to.
+ * <p>
+ * Symbolic references are stored as {@link FileMode#SYMLINK} entries, with the
+ * blob storing the name of the target reference.
+ * <p>
+ * Annotated tags also store the peeled object using a {@code GITLINK} entry
+ * with the suffix <code>" ^"</code> (space carrot), for example
+ * {@code "tags/v1.0"} stores the annotated tag object, while
+ * <code>"tags/v1.0 ^"</code> stores the commit the tag annotates.
+ * <p>
+ * {@code HEAD} is a special case and stored as {@code "..HEAD"}.
+ */
+public class RefTree {
+ /** Suffix applied to GITLINK to indicate its the peeled value of a tag. */
+ public static final String PEELED_SUFFIX = " ^"; //$NON-NLS-1$
+ static final String ROOT_DOTDOT = ".."; //$NON-NLS-1$
+
+ /**
+ * Create an empty reference tree.
+ *
+ * @return a new empty reference tree.
+ */
+ public static RefTree newEmptyTree() {
+ return new RefTree(DirCache.newInCore());
+ }
+
+ /**
+ * Load a reference tree.
+ *
+ * @param reader
+ * reader to scan the reference tree with.
+ * @param tree
+ * the tree to read.
+ * @return the ref tree read from the commit.
+ * @throws IOException
+ * the repository cannot be accessed through the reader.
+ * @throws CorruptObjectException
+ * a tree object is corrupt and cannot be read.
+ * @throws IncorrectObjectTypeException
+ * a tree object wasn't actually a tree.
+ * @throws MissingObjectException
+ * a reference tree object doesn't exist.
+ */
+ public static RefTree read(ObjectReader reader, RevTree tree)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ CorruptObjectException, IOException {
+ return new RefTree(DirCache.read(reader, tree));
+ }
+
+ private DirCache contents;
+ private Map<ObjectId, String> pendingBlobs;
+
+ private RefTree(DirCache dc) {
+ this.contents = dc;
+ }
+
+ /**
+ * Read one reference.
+ * <p>
+ * References are always returned peeled ({@link Ref#isPeeled()} is true).
+ * If the reference points to an annotated tag, the returned reference will
+ * be peeled and contain {@link Ref#getPeeledObjectId()}.
+ * <p>
+ * If the reference is a symbolic reference and the chain depth is less than
+ * {@link org.eclipse.jgit.lib.RefDatabase#MAX_SYMBOLIC_REF_DEPTH} the
+ * returned reference is resolved. If the chain depth is longer, the
+ * symbolic reference is returned without resolving.
+ *
+ * @param reader
+ * to access objects necessary to read the requested reference.
+ * @param name
+ * name of the reference to read.
+ * @return the reference; null if it does not exist.
+ * @throws IOException
+ * cannot read a symbolic reference target.
+ */
+ @Nullable
+ public Ref exactRef(ObjectReader reader, String name) throws IOException {
+ Ref r = readRef(reader, name);
+ if (r == null) {
+ return null;
+ } else if (r.isSymbolic()) {
+ return resolve(reader, r, 0);
+ }
+
+ DirCacheEntry p = contents.getEntry(peeledPath(name));
+ if (p != null && p.getRawMode() == TYPE_GITLINK) {
+ return new ObjectIdRef.PeeledTag(PACKED, r.getName(),
+ r.getObjectId(), p.getObjectId());
+ }
+ return r;
+ }
+
+ private Ref readRef(ObjectReader reader, String name) throws IOException {
+ DirCacheEntry e = contents.getEntry(refPath(name));
+ return e != null ? toRef(reader, e, name) : null;
+ }
+
+ private Ref toRef(ObjectReader reader, DirCacheEntry e, String name)
+ throws IOException {
+ int mode = e.getRawMode();
+ if (mode == TYPE_GITLINK) {
+ ObjectId id = e.getObjectId();
+ return new ObjectIdRef.PeeledNonTag(PACKED, name, id);
+ }
+
+ if (mode == TYPE_SYMLINK) {
+ ObjectId id = e.getObjectId();
+ String n = pendingBlobs != null ? pendingBlobs.get(id) : null;
+ if (n == null) {
+ byte[] bin = reader.open(id, OBJ_BLOB).getCachedBytes();
+ n = RawParseUtils.decode(bin);
+ }
+ Ref dst = new ObjectIdRef.Unpeeled(NEW, n, null);
+ return new SymbolicRef(name, dst);
+ }
+
+ return null; // garbage file or something; not a reference.
+ }
+
+ private Ref resolve(ObjectReader reader, Ref ref, int depth)
+ throws IOException {
+ if (ref.isSymbolic() && depth < MAX_SYMBOLIC_REF_DEPTH) {
+ Ref r = readRef(reader, ref.getTarget().getName());
+ if (r == null) {
+ return ref;
+ }
+ Ref dst = resolve(reader, r, depth + 1);
+ return new SymbolicRef(ref.getName(), dst);
+ }
+ return ref;
+ }
+
+ /**
+ * Attempt a batch of commands against this RefTree.
+ * <p>
+ * The batch is applied atomically, either all commands apply at once, or
+ * they all reject and the RefTree is left unmodified.
+ * <p>
+ * On success (when this method returns {@code true}) the command results
+ * are left as-is (probably {@code NOT_ATTEMPTED}). Result fields are set
+ * only when this method returns {@code false} to indicate failure.
+ *
+ * @param cmdList
+ * to apply. All commands should still have result NOT_ATTEMPTED.
+ * @return true if the commands applied; false if they were rejected.
+ */
+ public boolean apply(Collection<Command> cmdList) {
+ try {
+ DirCacheEditor ed = contents.editor();
+ for (Command cmd : cmdList) {
+ if (!isValidRef(cmd)) {
+ cmd.setResult(REJECTED_OTHER_REASON,
+ JGitText.get().funnyRefname);
+ Command.abort(cmdList, null);
+ return false;
+ }
+ apply(ed, cmd);
+ }
+ ed.finish();
+ return true;
+ } catch (DirCacheNameConflictException e) {
+ String r1 = refName(e.getPath1());
+ String r2 = refName(e.getPath2());
+ for (Command cmd : cmdList) {
+ if (r1.equals(cmd.getRefName())
+ || r2.equals(cmd.getRefName())) {
+ cmd.setResult(LOCK_FAILURE);
+ break;
+ }
+ }
+ Command.abort(cmdList, null);
+ return false;
+ } catch (LockFailureException e) {
+ Command.abort(cmdList, null);
+ return false;
+ }
+ }
+
+ private static boolean isValidRef(Command cmd) {
+ String n = cmd.getRefName();
+ return HEAD.equals(n) || Repository.isValidRefName(n);
+ }
+
+ private void apply(DirCacheEditor ed, final Command cmd) {
+ String path = refPath(cmd.getRefName());
+ Ref oldRef = cmd.getOldRef();
+ final Ref newRef = cmd.getNewRef();
+
+ if (newRef == null) {
+ checkRef(contents.getEntry(path), cmd);
+ ed.add(new DeletePath(path));
+ cleanupPeeledRef(ed, oldRef);
+ return;
+ }
+
+ if (newRef.isSymbolic()) {
+ final String dst = newRef.getTarget().getName();
+ ed.add(new PathEdit(path) {
+ @Override
+ public void apply(DirCacheEntry ent) {
+ checkRef(ent, cmd);
+ ObjectId id = Command.symref(dst);
+ ent.setFileMode(SYMLINK);
+ ent.setObjectId(id);
+ if (pendingBlobs == null) {
+ pendingBlobs = new HashMap<>(4);
+ }
+ pendingBlobs.put(id, dst);
+ }
+ }.setReplace(false));
+ cleanupPeeledRef(ed, oldRef);
+ return;
+ }
+
+ ed.add(new PathEdit(path) {
+ @Override
+ public void apply(DirCacheEntry ent) {
+ checkRef(ent, cmd);
+ ent.setFileMode(GITLINK);
+ ent.setObjectId(newRef.getObjectId());
+ }
+ }.setReplace(false));
+
+ if (newRef.getPeeledObjectId() != null) {
+ ed.add(new PathEdit(peeledPath(newRef.getName())) {
+ @Override
+ public void apply(DirCacheEntry ent) {
+ ent.setFileMode(GITLINK);
+ ent.setObjectId(newRef.getPeeledObjectId());
+ }
+ }.setReplace(false));
+ } else {
+ cleanupPeeledRef(ed, oldRef);
+ }
+ }
+
+ private static void checkRef(@Nullable DirCacheEntry ent, Command cmd) {
+ if (!cmd.checkRef(ent)) {
+ cmd.setResult(LOCK_FAILURE);
+ throw new LockFailureException();
+ }
+ }
+
+ private static void cleanupPeeledRef(DirCacheEditor ed, Ref ref) {
+ if (ref != null && !ref.isSymbolic()
+ && (!ref.isPeeled() || ref.getPeeledObjectId() != null)) {
+ ed.add(new DeletePath(peeledPath(ref.getName())));
+ }
+ }
+
+ /**
+ * Convert a path name in a RefTree to the reference name known by Git.
+ *
+ * @param path
+ * name read from the RefTree structure, for example
+ * {@code "heads/master"}.
+ * @return reference name for the path, {@code "refs/heads/master"}.
+ */
+ public static String refName(String path) {
+ if (path.startsWith(ROOT_DOTDOT)) {
+ return path.substring(2);
+ }
+ return R_REFS + path;
+ }
+
+ static String refPath(String name) {
+ if (name.startsWith(R_REFS)) {
+ return name.substring(R_REFS.length());
+ }
+ return ROOT_DOTDOT + name;
+ }
+
+ private static String peeledPath(String name) {
+ return refPath(name) + PEELED_SUFFIX;
+ }
+
+ /**
+ * Write this reference tree.
+ *
+ * @param inserter
+ * inserter to use when writing trees to the object database.
+ * Caller is responsible for flushing the inserter before trying
+ * to read the objects, or exposing them through a reference.
+ * @return the top level tree.
+ * @throws IOException
+ * a tree could not be written.
+ */
+ public ObjectId writeTree(ObjectInserter inserter) throws IOException {
+ if (pendingBlobs != null) {
+ for (String s : pendingBlobs.values()) {
+ inserter.insert(OBJ_BLOB, encode(s));
+ }
+ pendingBlobs = null;
+ }
+ return contents.writeTree(inserter);
+ }
+
+ /** @return a deep copy of this RefTree. */
+ public RefTree copy() {
+ RefTree r = new RefTree(DirCache.newInCore());
+ DirCacheBuilder b = r.contents.builder();
+ for (int i = 0; i < contents.getEntryCount(); i++) {
+ b.add(new DirCacheEntry(contents.getEntry(i)));
+ }
+ b.finish();
+ if (pendingBlobs != null) {
+ r.pendingBlobs = new HashMap<>(pendingBlobs);
+ }
+ return r;
+ }
+
+ private static class LockFailureException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+ }
+}
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
new file mode 100644
index 0000000000..a55a9f51e7
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeBatch.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2016, 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.reftree;
+
+import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
+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_NONFASTFORWARD;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE;
+import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
+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.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/** Batch update a {@link RefTreeDatabase}. */
+class RefTreeBatch extends BatchRefUpdate {
+ private final RefTreeDatabase refdb;
+ private Ref src;
+ private ObjectId parentCommitId;
+ private ObjectId parentTreeId;
+ private RefTree tree;
+ private PersonIdent author;
+ private ObjectId newCommitId;
+
+ RefTreeBatch(RefTreeDatabase refdb) {
+ super(refdb);
+ this.refdb = refdb;
+ }
+
+ @Override
+ public void execute(RevWalk rw, ProgressMonitor monitor)
+ throws IOException {
+ List<Command> todo = new ArrayList<>(getCommands().size());
+ for (ReceiveCommand c : getCommands()) {
+ if (!isAllowNonFastForwards()) {
+ if (c.getType() == UPDATE) {
+ c.updateType(rw);
+ }
+ if (c.getType() == UPDATE_NONFASTFORWARD) {
+ c.setResult(REJECTED_NONFASTFORWARD);
+ ReceiveCommand.abort(getCommands());
+ return;
+ }
+ }
+ todo.add(new Command(rw, c));
+ }
+ init(rw);
+ execute(rw, todo);
+ }
+
+ void init(RevWalk rw) throws IOException {
+ src = refdb.getBootstrap().exactRef(refdb.getTxnCommitted());
+ if (src != null && src.getObjectId() != null) {
+ RevCommit c = rw.parseCommit(src.getObjectId());
+ parentCommitId = c;
+ parentTreeId = c.getTree();
+ tree = RefTree.read(rw.getObjectReader(), c.getTree());
+ } else {
+ parentCommitId = ObjectId.zeroId();
+ parentTreeId = new ObjectInserter.Formatter()
+ .idFor(OBJ_TREE, new byte[] {});
+ tree = RefTree.newEmptyTree();
+ }
+ }
+
+ @Nullable
+ Ref exactRef(ObjectReader reader, String name) throws IOException {
+ return tree.exactRef(reader, name);
+ }
+
+ /**
+ * Execute an update from {@link RefTreeUpdate} or {@link RefTreeRename}.
+ *
+ * @param rw
+ * current RevWalk handling the update or rename.
+ * @param todo
+ * commands to execute. Must never be a bootstrap reference name.
+ * @throws IOException
+ * the storage system is unable to read or write data.
+ */
+ void execute(RevWalk rw, List<Command> todo) throws IOException {
+ for (Command c : todo) {
+ if (c.getResult() != NOT_ATTEMPTED) {
+ Command.abort(todo, null);
+ return;
+ }
+ if (refdb.conflictsWithBootstrap(c.getRefName())) {
+ c.setResult(REJECTED_OTHER_REASON, MessageFormat
+ .format(JGitText.get().invalidRefName, c.getRefName()));
+ Command.abort(todo, null);
+ return;
+ }
+ }
+
+ if (apply(todo) && newCommitId != null) {
+ commit(rw, todo);
+ }
+ }
+
+ private boolean apply(List<Command> todo) throws IOException {
+ if (!tree.apply(todo)) {
+ // apply set rejection information on commands.
+ return false;
+ }
+
+ Repository repo = refdb.getRepository();
+ try (ObjectInserter ins = repo.newObjectInserter()) {
+ CommitBuilder b = new CommitBuilder();
+ b.setTreeId(tree.writeTree(ins));
+ if (parentTreeId.equals(b.getTreeId())) {
+ for (Command c : todo) {
+ c.setResult(OK);
+ }
+ return true;
+ }
+ if (!parentCommitId.equals(ObjectId.zeroId())) {
+ b.setParentId(parentCommitId);
+ }
+
+ author = getRefLogIdent();
+ if (author == null) {
+ author = new PersonIdent(repo);
+ }
+ b.setAuthor(author);
+ b.setCommitter(author);
+ b.setMessage(getRefLogMessage());
+ newCommitId = ins.insert(b);
+ ins.flush();
+ }
+ return true;
+ }
+
+ private void commit(RevWalk rw, List<Command> todo) throws IOException {
+ ReceiveCommand commit = new ReceiveCommand(
+ parentCommitId, newCommitId,
+ refdb.getTxnCommitted());
+ updateBootstrap(rw, commit);
+
+ if (commit.getResult() == OK) {
+ for (Command c : todo) {
+ c.setResult(OK);
+ }
+ } else {
+ Command.abort(todo, commit.getResult().name());
+ }
+ }
+
+ private void updateBootstrap(RevWalk rw, ReceiveCommand commit)
+ throws IOException {
+ BatchRefUpdate u = refdb.getBootstrap().newBatchUpdate();
+ u.setAllowNonFastForwards(true);
+ u.setPushCertificate(getPushCertificate());
+ if (isRefLogDisabled()) {
+ u.disableRefLog();
+ } else {
+ u.setRefLogIdent(author);
+ u.setRefLogMessage(getRefLogMessage(), false);
+ }
+ u.addCommand(commit);
+ u.execute(rw, NullProgressMonitor.INSTANCE);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java
new file mode 100644
index 0000000000..dc60311102
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeDatabase.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2016, 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.reftree;
+
+import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Ref.Storage;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefRename;
+import org.eclipse.jgit.lib.RefUpdate;
+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.util.RefList;
+import org.eclipse.jgit.util.RefMap;
+
+/**
+ * Reference database backed by a {@link RefTree}.
+ * <p>
+ * The storage for RefTreeDatabase has two parts. The main part is a native Git
+ * tree object stored under the {@code refs/txn} namespace. To avoid cycles,
+ * references to {@code refs/txn} are not stored in that tree object, but
+ * instead in a "bootstrap" layer, which is a separate {@link RefDatabase} such
+ * as {@link org.eclipse.jgit.internal.storage.file.RefDirectory} using local
+ * reference files inside of {@code $GIT_DIR/refs}.
+ */
+public class RefTreeDatabase extends RefDatabase {
+ private final Repository repo;
+ private final RefDatabase bootstrap;
+ private final String txnCommitted;
+
+ @Nullable
+ private final String txnNamespace;
+ private volatile Scanner.Result refs;
+
+ /**
+ * Create a RefTreeDb for a repository.
+ *
+ * @param repo
+ * the repository using references in this database.
+ * @param bootstrap
+ * bootstrap reference database storing the references that
+ * anchor the {@link RefTree}.
+ */
+ public RefTreeDatabase(Repository repo, RefDatabase bootstrap) {
+ Config cfg = repo.getConfig();
+ String committed = cfg.getString("reftree", null, "committedRef"); //$NON-NLS-1$ //$NON-NLS-2$
+ if (committed == null || committed.isEmpty()) {
+ committed = "refs/txn/committed"; //$NON-NLS-1$
+ }
+
+ this.repo = repo;
+ this.bootstrap = bootstrap;
+ this.txnNamespace = initNamespace(committed);
+ this.txnCommitted = committed;
+ }
+
+ /**
+ * Create a RefTreeDb for a repository.
+ *
+ * @param repo
+ * the repository using references in this database.
+ * @param bootstrap
+ * bootstrap reference database storing the references that
+ * anchor the {@link RefTree}.
+ * @param txnCommitted
+ * name of the bootstrap reference holding the committed RefTree.
+ */
+ public RefTreeDatabase(Repository repo, RefDatabase bootstrap,
+ String txnCommitted) {
+ this.repo = repo;
+ this.bootstrap = bootstrap;
+ this.txnNamespace = initNamespace(txnCommitted);
+ this.txnCommitted = txnCommitted;
+ }
+
+ private static String initNamespace(String committed) {
+ int s = committed.lastIndexOf('/');
+ if (s < 0) {
+ return null;
+ }
+ return committed.substring(0, s + 1); // Keep trailing '/'.
+ }
+
+ Repository getRepository() {
+ return repo;
+ }
+
+ /**
+ * @return the bootstrap reference database, which must be used to access
+ * {@link #getTxnCommitted()}, {@link #getTxnNamespace()}.
+ */
+ public RefDatabase getBootstrap() {
+ return bootstrap;
+ }
+
+ /** @return name of bootstrap reference anchoring committed RefTree. */
+ public String getTxnCommitted() {
+ return txnCommitted;
+ }
+
+ /**
+ * @return namespace used by bootstrap layer, e.g. {@code refs/txn/}.
+ * Always ends in {@code '/'}.
+ */
+ @Nullable
+ public String getTxnNamespace() {
+ return txnNamespace;
+ }
+
+ @Override
+ public void create() throws IOException {
+ bootstrap.create();
+ }
+
+ @Override
+ public boolean performsAtomicTransactions() {
+ return true;
+ }
+
+ @Override
+ public void refresh() {
+ bootstrap.refresh();
+ }
+
+ @Override
+ public void close() {
+ refs = null;
+ bootstrap.close();
+ }
+
+ @Override
+ public Ref getRef(String name) throws IOException {
+ return findRef(getRefs(ALL), name);
+ }
+
+ @Override
+ public Ref exactRef(String name) throws IOException {
+ if (conflictsWithBootstrap(name)) {
+ return null;
+ }
+
+ boolean partial = false;
+ Ref src = bootstrap.exactRef(txnCommitted);
+ Scanner.Result c = refs;
+ if (c == null || !c.refTreeId.equals(idOf(src))) {
+ c = Scanner.scanRefTree(repo, src, prefixOf(name), false);
+ partial = true;
+ }
+
+ Ref r = c.all.get(name);
+ if (r != null && r.isSymbolic()) {
+ r = c.sym.get(name);
+ if (partial && r.getObjectId() == null) {
+ // Attempting exactRef("HEAD") with partial scan will leave
+ // an unresolved symref as its target e.g. refs/heads/master
+ // was not read by the partial scan. Scan everything instead.
+ return getRefs(ALL).get(name);
+ }
+ }
+ return r;
+ }
+
+ private static String prefixOf(String name) {
+ int s = name.lastIndexOf('/');
+ if (s >= 0) {
+ return name.substring(0, s);
+ }
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public Map<String, Ref> getRefs(String prefix) throws IOException {
+ if (!prefix.isEmpty() && prefix.charAt(prefix.length() - 1) != '/') {
+ return new HashMap<>(0);
+ }
+
+ Ref src = bootstrap.exactRef(txnCommitted);
+ Scanner.Result c = refs;
+ if (c == null || !c.refTreeId.equals(idOf(src))) {
+ c = Scanner.scanRefTree(repo, src, prefix, true);
+ if (prefix.isEmpty()) {
+ refs = c;
+ }
+ }
+ return new RefMap(prefix, RefList.<Ref> emptyList(), c.all, c.sym);
+ }
+
+ private static ObjectId idOf(@Nullable Ref src) {
+ return src != null && src.getObjectId() != null
+ ? src.getObjectId()
+ : ObjectId.zeroId();
+ }
+
+ @Override
+ public List<Ref> getAdditionalRefs() throws IOException {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Ref peel(Ref ref) throws IOException {
+ Ref i = ref.getLeaf();
+ ObjectId id = i.getObjectId();
+ if (i.isPeeled() || id == null) {
+ return ref;
+ }
+ try (RevWalk rw = new RevWalk(repo)) {
+ RevObject obj = rw.parseAny(id);
+ if (obj instanceof RevTag) {
+ ObjectId p = rw.peel(obj).copy();
+ i = new ObjectIdRef.PeeledTag(PACKED, i.getName(), id, p);
+ } else {
+ i = new ObjectIdRef.PeeledNonTag(PACKED, i.getName(), id);
+ }
+ }
+ return recreate(ref, i);
+ }
+
+ private static Ref recreate(Ref old, Ref leaf) {
+ if (old.isSymbolic()) {
+ Ref dst = recreate(old.getTarget(), leaf);
+ return new SymbolicRef(old.getName(), dst);
+ }
+ return leaf;
+ }
+
+ @Override
+ public boolean isNameConflicting(String name) throws IOException {
+ return conflictsWithBootstrap(name)
+ || !getConflictingNames(name).isEmpty();
+ }
+
+ @Override
+ public BatchRefUpdate newBatchUpdate() {
+ return new RefTreeBatch(this);
+ }
+
+ @Override
+ public RefUpdate newUpdate(String name, boolean detach) throws IOException {
+ if (conflictsWithBootstrap(name)) {
+ return new AlwaysFailUpdate(this, name);
+ }
+
+ Ref r = exactRef(name);
+ if (r == null) {
+ r = new ObjectIdRef.Unpeeled(Storage.NEW, name, null);
+ }
+
+ boolean detaching = detach && r.isSymbolic();
+ if (detaching) {
+ r = new ObjectIdRef.Unpeeled(LOOSE, name, r.getObjectId());
+ }
+
+ RefTreeUpdate u = new RefTreeUpdate(this, r);
+ if (detaching) {
+ u.setDetachingSymbolicRef();
+ }
+ return u;
+ }
+
+ @Override
+ public RefRename newRename(String fromName, String toName)
+ throws IOException {
+ RefUpdate from = newUpdate(fromName, true);
+ RefUpdate to = newUpdate(toName, true);
+ return new RefTreeRename(this, from, to);
+ }
+
+ boolean conflictsWithBootstrap(String name) {
+ if (txnNamespace != null && name.startsWith(txnNamespace)) {
+ return true;
+ } else if (txnCommitted.equals(name)) {
+ return true;
+ } else if (name.length() > txnCommitted.length()
+ && name.charAt(txnCommitted.length()) == '/'
+ && name.startsWith(txnCommitted)) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeNames.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeNames.java
new file mode 100644
index 0000000000..239a745277
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeNames.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016, 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.reftree;
+
+import static org.eclipse.jgit.lib.RefDatabase.ALL;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+
+/** Magic reference name logic for RefTrees. */
+public class RefTreeNames {
+ /**
+ * Suffix used on a {@link RefTreeDatabase#getTxnNamespace()} for user data.
+ * <p>
+ * A {@link RefTreeDatabase}'s namespace may include a subspace (e.g.
+ * {@code "refs/txn/stage/"}) containing commit objects from the usual user
+ * portion of the repository (e.g. {@code "refs/heads/"}). These should be
+ * packed by the garbage collector alongside other user content rather than
+ * with the RefTree.
+ */
+ private static final String STAGE = "stage/"; //$NON-NLS-1$
+
+ /**
+ * Determine if the reference is likely to be a RefTree.
+ *
+ * @param refdb
+ * database instance.
+ * @param ref
+ * reference name.
+ * @return {@code true} if the reference is a RefTree.
+ */
+ public static boolean isRefTree(RefDatabase refdb, String ref) {
+ if (refdb instanceof RefTreeDatabase) {
+ RefTreeDatabase b = (RefTreeDatabase) refdb;
+ if (ref.equals(b.getTxnCommitted())) {
+ return true;
+ }
+
+ String namespace = b.getTxnNamespace();
+ if (namespace != null
+ && ref.startsWith(namespace)
+ && !ref.startsWith(namespace + STAGE)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Snapshot all references from a RefTreeDatabase and its bootstrap.
+ * <p>
+ * There may be name conflicts with multiple {@link Ref} objects containing
+ * the same name in the returned collection.
+ *
+ * @param refdb
+ * database instance.
+ * @return all known references.
+ * @throws IOException
+ * references cannot be enumerated.
+ */
+ public static Collection<Ref> allRefs(RefDatabase refdb)
+ throws IOException {
+ Collection<Ref> refs = refdb.getRefs(ALL).values();
+ if (!(refdb instanceof RefTreeDatabase)) {
+ return refs;
+ }
+
+ RefDatabase bootstrap = ((RefTreeDatabase) refdb).getBootstrap();
+ Collection<Ref> br = bootstrap.getRefs(ALL).values();
+ List<Ref> all = new ArrayList<>(refs.size() + br.size());
+ all.addAll(refs);
+ all.addAll(br);
+ return all;
+ }
+
+ private RefTreeNames() {
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeRename.java
new file mode 100644
index 0000000000..5fd7ecdd79
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeRename.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2016, 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.reftree;
+
+import static org.eclipse.jgit.lib.Constants.HEAD;
+import static org.eclipse.jgit.lib.RefUpdate.Result.REJECTED;
+import static org.eclipse.jgit.lib.RefUpdate.Result.RENAMED;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefRename;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+/** Single reference rename to {@link RefTreeDatabase}. */
+class RefTreeRename extends RefRename {
+ private final RefTreeDatabase refdb;
+
+ RefTreeRename(RefTreeDatabase refdb, RefUpdate src, RefUpdate dst) {
+ super(src, dst);
+ this.refdb = refdb;
+ }
+
+ @Override
+ protected Result doRename() throws IOException {
+ try (RevWalk rw = new RevWalk(refdb.getRepository())) {
+ RefTreeBatch batch = new RefTreeBatch(refdb);
+ batch.setRefLogIdent(getRefLogIdent());
+ batch.setRefLogMessage(getRefLogMessage(), false);
+ batch.init(rw);
+
+ Ref head = batch.exactRef(rw.getObjectReader(), HEAD);
+ Ref oldRef = batch.exactRef(rw.getObjectReader(), source.getName());
+ if (oldRef == null) {
+ return REJECTED;
+ }
+
+ Ref newRef = asNew(oldRef);
+ List<Command> mv = new ArrayList<>(3);
+ mv.add(new Command(oldRef, null));
+ mv.add(new Command(null, newRef));
+ if (head != null && head.isSymbolic()
+ && head.getTarget().getName().equals(oldRef.getName())) {
+ mv.add(new Command(
+ head,
+ new SymbolicRef(head.getName(), newRef)));
+ }
+ batch.execute(rw, mv);
+ return RefTreeUpdate.translate(mv.get(1).getResult(), RENAMED);
+ }
+ }
+
+ private Ref asNew(Ref src) {
+ String name = destination.getName();
+ if (src.isSymbolic()) {
+ return new SymbolicRef(name, src.getTarget());
+ }
+
+ ObjectId peeled = src.getPeeledObjectId();
+ if (peeled != null) {
+ return new ObjectIdRef.PeeledTag(
+ src.getStorage(),
+ name,
+ src.getObjectId(),
+ peeled);
+ }
+
+ return new ObjectIdRef.PeeledNonTag(
+ src.getStorage(),
+ name,
+ src.getObjectId());
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeUpdate.java
new file mode 100644
index 0000000000..8829c1156a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/RefTreeUpdate.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2016, 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.reftree;
+
+import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.RefUpdate;
+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;
+
+/** Single reference update to {@link RefTreeDatabase}. */
+class RefTreeUpdate extends RefUpdate {
+ private final RefTreeDatabase refdb;
+ private RevWalk rw;
+ private RefTreeBatch batch;
+ private Ref oldRef;
+
+ RefTreeUpdate(RefTreeDatabase refdb, Ref ref) {
+ super(ref);
+ this.refdb = refdb;
+ setCheckConflicting(false); // Done automatically by doUpdate.
+ }
+
+ @Override
+ protected RefDatabase getRefDatabase() {
+ return refdb;
+ }
+
+ @Override
+ protected Repository getRepository() {
+ return refdb.getRepository();
+ }
+
+ @Override
+ protected boolean tryLock(boolean deref) throws IOException {
+ rw = new RevWalk(getRepository());
+ batch = new RefTreeBatch(refdb);
+ batch.init(rw);
+ oldRef = batch.exactRef(rw.getObjectReader(), getName());
+ if (oldRef != null && oldRef.getObjectId() != null) {
+ setOldObjectId(oldRef.getObjectId());
+ } else if (oldRef == null && getExpectedOldObjectId() != null) {
+ setOldObjectId(ObjectId.zeroId());
+ }
+ return true;
+ }
+
+ @Override
+ protected void unlock() {
+ batch = null;
+ if (rw != null) {
+ rw.close();
+ rw = null;
+ }
+ }
+
+ @Override
+ protected Result doUpdate(Result desiredResult) throws IOException {
+ return run(newRef(getName(), getNewObjectId()), desiredResult);
+ }
+
+ private Ref newRef(String name, ObjectId id)
+ throws MissingObjectException, IOException {
+ RevObject o = rw.parseAny(id);
+ if (o instanceof RevTag) {
+ RevObject p = rw.peel(o);
+ return new ObjectIdRef.PeeledTag(LOOSE, name, id, p.copy());
+ }
+ return new ObjectIdRef.PeeledNonTag(LOOSE, name, id);
+ }
+
+ @Override
+ protected Result doDelete(Result desiredResult) throws IOException {
+ return run(null, desiredResult);
+ }
+
+ @Override
+ protected Result doLink(String target) throws IOException {
+ Ref dst = new ObjectIdRef.Unpeeled(NEW, target, null);
+ SymbolicRef n = new SymbolicRef(getName(), dst);
+ Result desiredResult = getRef().getStorage() == NEW
+ ? Result.NEW
+ : Result.FORCED;
+ return run(n, desiredResult);
+ }
+
+ private Result run(@Nullable Ref newRef, Result desiredResult)
+ throws IOException {
+ Command c = new Command(oldRef, newRef);
+ batch.setRefLogIdent(getRefLogIdent());
+ batch.setRefLogMessage(getRefLogMessage(), isRefLogIncludingResult());
+ batch.execute(rw, Collections.singletonList(c));
+ return translate(c.getResult(), desiredResult);
+ }
+
+ static Result translate(ReceiveCommand.Result r, Result desiredResult) {
+ switch (r) {
+ case OK:
+ return desiredResult;
+
+ case LOCK_FAILURE:
+ return Result.LOCK_FAILURE;
+
+ case NOT_ATTEMPTED:
+ return Result.NOT_ATTEMPTED;
+
+ case REJECTED_MISSING_OBJECT:
+ return Result.IO_FAILURE;
+
+ case REJECTED_CURRENT_BRANCH:
+ return Result.REJECTED_CURRENT_BRANCH;
+
+ case REJECTED_OTHER_REASON:
+ case REJECTED_NOCREATE:
+ case REJECTED_NODELETE:
+ case REJECTED_NONFASTFORWARD:
+ default:
+ return Result.REJECTED;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java
new file mode 100644
index 0000000000..d383abf316
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftree/Scanner.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2016, 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.reftree;
+
+import static org.eclipse.jgit.lib.RefDatabase.MAX_SYMBOLIC_REF_DEPTH;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.R_REFS;
+import static org.eclipse.jgit.lib.Constants.encode;
+import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
+import static org.eclipse.jgit.lib.Ref.Storage.NEW;
+import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.SymbolicRef;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.AbstractTreeIterator;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.util.Paths;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.RefList;
+
+/** A tree parser that extracts references from a {@link RefTree}. */
+class Scanner {
+ private static final int MAX_SYMLINK_BYTES = 10 << 10;
+ private static final byte[] BINARY_R_REFS = encode(R_REFS);
+ private static final byte[] REFS_DOT_DOT = encode("refs/.."); //$NON-NLS-1$
+
+ static class Result {
+ final ObjectId refTreeId;
+ final RefList<Ref> all;
+ final RefList<Ref> sym;
+
+ Result(ObjectId id, RefList<Ref> all, RefList<Ref> sym) {
+ this.refTreeId = id;
+ this.all = all;
+ this.sym = sym;
+ }
+ }
+
+ /**
+ * Scan a {@link RefTree} and parse entries into {@link Ref} instances.
+ *
+ * @param repo
+ * source repository containing the commit and tree objects that
+ * make up the RefTree.
+ * @param src
+ * bootstrap reference such as {@code refs/txn/committed} to read
+ * the reference tree tip from. The current ObjectId will be
+ * included in {@link Result#refTreeId}.
+ * @param prefix
+ * if non-empty a reference prefix to scan only a subdirectory.
+ * For example {@code prefix = "refs/heads/"} will limit the scan
+ * to only the {@code "heads"} directory of the RefTree, avoiding
+ * other directories like {@code "tags"}. Empty string reads all
+ * entries in the RefTree.
+ * @param recursive
+ * if true recurse into subdirectories of the reference tree;
+ * false to read only one level. Callers may use false during an
+ * implementation of {@code exactRef(String)} where only one
+ * reference is needed out of a specific subtree.
+ * @return sorted list of references after parsing.
+ * @throws IOException
+ * tree cannot be accessed from the repository.
+ */
+ static Result scanRefTree(Repository repo, @Nullable Ref src, String prefix,
+ boolean recursive) throws IOException {
+ RefList.Builder<Ref> all = new RefList.Builder<>();
+ RefList.Builder<Ref> sym = new RefList.Builder<>();
+
+ ObjectId srcId;
+ if (src != null && src.getObjectId() != null) {
+ try (ObjectReader reader = repo.newObjectReader()) {
+ srcId = src.getObjectId();
+ scan(reader, srcId, prefix, recursive, all, sym);
+ }
+ } else {
+ srcId = ObjectId.zeroId();
+ }
+
+ RefList<Ref> aList = all.toRefList();
+ for (int idx = 0; idx < sym.size();) {
+ Ref s = sym.get(idx);
+ Ref r = resolve(s, 0, aList);
+ if (r != null) {
+ sym.set(idx++, r);
+ } else {
+ // Remove broken symbolic reference, they don't exist.
+ sym.remove(idx);
+ int rm = aList.find(s.getName());
+ if (0 <= rm) {
+ aList = aList.remove(rm);
+ }
+ }
+ }
+ return new Result(srcId, aList, sym.toRefList());
+ }
+
+ private static void scan(ObjectReader reader, AnyObjectId srcId,
+ String prefix, boolean recursive,
+ RefList.Builder<Ref> all, RefList.Builder<Ref> sym)
+ throws IncorrectObjectTypeException, IOException {
+ CanonicalTreeParser p = createParserAtPath(reader, srcId, prefix);
+ if (p == null) {
+ return;
+ }
+
+ while (!p.eof()) {
+ int mode = p.getEntryRawMode();
+ if (mode == TYPE_TREE) {
+ if (recursive) {
+ p = p.createSubtreeIterator(reader);
+ } else {
+ p = p.next();
+ }
+ continue;
+ }
+
+ if (!curElementHasPeelSuffix(p)) {
+ Ref r = toRef(reader, mode, p);
+ if (r != null) {
+ all.add(r);
+ if (r.isSymbolic()) {
+ sym.add(r);
+ }
+ }
+ } else if (mode == TYPE_GITLINK) {
+ peel(all, p);
+ }
+ p = p.next();
+ }
+ }
+
+ private static CanonicalTreeParser createParserAtPath(ObjectReader reader,
+ AnyObjectId srcId, String prefix) throws IOException {
+ ObjectId root = toTree(reader, srcId);
+ if (prefix.isEmpty()) {
+ return new CanonicalTreeParser(BINARY_R_REFS, reader, root);
+ }
+
+ String dir = RefTree.refPath(Paths.stripTrailingSeparator(prefix));
+ TreeWalk tw = TreeWalk.forPath(reader, dir, root);
+ if (tw == null || !tw.isSubtree()) {
+ return null;
+ }
+
+ ObjectId id = tw.getObjectId(0);
+ return new CanonicalTreeParser(encode(prefix), reader, id);
+ }
+
+ private static Ref resolve(Ref ref, int depth, RefList<Ref> refs)
+ throws IOException {
+ if (!ref.isSymbolic()) {
+ return ref;
+ } else if (MAX_SYMBOLIC_REF_DEPTH <= depth) {
+ return null;
+ }
+
+ Ref r = refs.get(ref.getTarget().getName());
+ if (r == null) {
+ return ref;
+ }
+
+ Ref dst = resolve(r, depth + 1, refs);
+ if (dst == null) {
+ return null;
+ }
+ return new SymbolicRef(ref.getName(), dst);
+ }
+
+ @SuppressWarnings("resource")
+ private static RevTree toTree(ObjectReader reader, AnyObjectId id)
+ throws IOException {
+ return new RevWalk(reader).parseTree(id);
+ }
+
+ private static boolean curElementHasPeelSuffix(AbstractTreeIterator itr) {
+ int n = itr.getEntryPathLength();
+ byte[] c = itr.getEntryPathBuffer();
+ return n > 2 && c[n - 2] == ' ' && c[n - 1] == '^';
+ }
+
+ private static void peel(RefList.Builder<Ref> all, CanonicalTreeParser p) {
+ String name = refName(p, true);
+ for (int idx = all.size() - 1; 0 <= idx; idx--) {
+ Ref r = all.get(idx);
+ int cmp = r.getName().compareTo(name);
+ if (cmp == 0) {
+ all.set(idx, new ObjectIdRef.PeeledTag(PACKED, r.getName(),
+ r.getObjectId(), p.getEntryObjectId()));
+ break;
+ } else if (cmp < 0) {
+ // Stray peeled name without matching base name; skip entry.
+ break;
+ }
+ }
+ }
+
+ private static Ref toRef(ObjectReader reader, int mode,
+ CanonicalTreeParser p) throws IOException {
+ if (mode == TYPE_GITLINK) {
+ String name = refName(p, false);
+ ObjectId id = p.getEntryObjectId();
+ return new ObjectIdRef.PeeledNonTag(PACKED, name, id);
+
+ } else if (mode == TYPE_SYMLINK) {
+ ObjectId id = p.getEntryObjectId();
+ byte[] bin = reader.open(id, OBJ_BLOB)
+ .getCachedBytes(MAX_SYMLINK_BYTES);
+ String dst = RawParseUtils.decode(bin);
+ Ref trg = new ObjectIdRef.Unpeeled(NEW, dst, null);
+ String name = refName(p, false);
+ return new SymbolicRef(name, trg);
+ }
+ return null;
+ }
+
+ private static String refName(CanonicalTreeParser p, boolean peel) {
+ byte[] buf = p.getEntryPathBuffer();
+ int len = p.getEntryPathLength();
+ if (peel) {
+ len -= 2;
+ }
+ int ptr = 0;
+ if (RawParseUtils.match(buf, ptr, REFS_DOT_DOT) > 0) {
+ ptr = 7;
+ }
+ return RawParseUtils.decode(buf, ptr, len);
+ }
+
+ private Scanner() {
+ }
+}
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 45dd7ee1ac..670f9a9e14 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
@@ -109,7 +109,8 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
int pathStart = 8;
int lineEnd = RawParseUtils.nextLF(content, pathStart);
- if (content[lineEnd - 1] == '\n')
+ while (content[lineEnd - 1] == '\n' ||
+ (content[lineEnd - 1] == '\r' && SystemReader.getInstance().isWindows()))
lineEnd--;
if (lineEnd == pathStart)
throw new IOException(MessageFormat.format(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java
index cbb2f5b856..7d52991df0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java
@@ -79,7 +79,15 @@ public class BlobBasedConfig extends Config {
public BlobBasedConfig(Config base, final byte[] blob)
throws ConfigInvalidException {
super(base);
- fromText(RawParseUtils.decode(blob));
+ final String decoded;
+ if (blob.length >= 3 && blob[0] == (byte) 0xEF
+ && blob[1] == (byte) 0xBB && blob[2] == (byte) 0xBF) {
+ decoded = RawParseUtils.decode(RawParseUtils.UTF8_CHARSET,
+ blob, 3, blob.length);
+ } else {
+ decoded = RawParseUtils.decode(blob);
+ }
+ fromText(decoded);
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java
deleted file mode 100644
index 6811417ee0..0000000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/FileTreeEntry.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-
-/**
- * A representation of a file (blob) object in a {@link Tree}.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
- */
-@Deprecated
-public class FileTreeEntry extends TreeEntry {
- private FileMode mode;
-
- /**
- * Constructor for a File (blob) object.
- *
- * @param parent
- * The {@link Tree} holding this object (or null)
- * @param id
- * the SHA-1 of the blob (or null for a yet unhashed file)
- * @param nameUTF8
- * raw object name in the parent tree
- * @param execute
- * true if the executable flag is set
- */
- public FileTreeEntry(final Tree parent, final ObjectId id,
- final byte[] nameUTF8, final boolean execute) {
- super(parent, id, nameUTF8);
- setExecutable(execute);
- }
-
- public FileMode getMode() {
- return mode;
- }
-
- /**
- * @return true if this file is executable
- */
- public boolean isExecutable() {
- return getMode().equals(FileMode.EXECUTABLE_FILE);
- }
-
- /**
- * @param execute set/reset the executable flag
- */
- public void setExecutable(final boolean execute) {
- mode = execute ? FileMode.EXECUTABLE_FILE : FileMode.REGULAR_FILE;
- }
-
- /**
- * @return an {@link ObjectLoader} that will return the data
- * @throws IOException
- */
- public ObjectLoader openReader() throws IOException {
- return getRepository().open(getId(), Constants.OBJ_BLOB);
- }
-
- public String toString() {
- final StringBuilder r = new StringBuilder();
- r.append(ObjectId.toString(getId()));
- r.append(' ');
- r.append(isExecutable() ? 'X' : 'F');
- r.append(' ');
- r.append(getFullName());
- return r.toString();
- }
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
index a7a67a8812..0b5efd77d4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
@@ -44,21 +44,58 @@
package org.eclipse.jgit.lib;
-import static org.eclipse.jgit.util.RawParseUtils.match;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
+import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
+import static org.eclipse.jgit.lib.Constants.OBJ_BAD;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
+import static org.eclipse.jgit.lib.Constants.OBJ_TAG;
+import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_DATE;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_EMAIL;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_OBJECT_SHA1;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_PARENT_SHA1;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TIMEZONE;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_TREE_SHA1;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.BAD_UTF8;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.DUPLICATE_ENTRIES;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.EMPTY_NAME;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.FULL_PATHNAME;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOT;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTDOT;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.HAS_DOTGIT;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_AUTHOR;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_COMMITTER;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_EMAIL;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_OBJECT;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_SPACE_BEFORE_DATE;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TAG_ENTRY;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TREE;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.MISSING_TYPE_ENTRY;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.NULL_SHA1;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.TREE_NOT_SORTED;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.UNKNOWN_TYPE;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.WIN32_BAD_NAME;
+import static org.eclipse.jgit.lib.ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE;
+import static org.eclipse.jgit.util.Paths.compare;
+import static org.eclipse.jgit.util.Paths.compareSameName;
import static org.eclipse.jgit.util.RawParseUtils.nextLF;
import static org.eclipse.jgit.util.RawParseUtils.parseBase10;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.text.MessageFormat;
+import java.text.Normalizer;
+import java.util.EnumSet;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.util.MutableInteger;
import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.StringUtils;
/**
* Verifies that an object is formatted correctly.
@@ -99,31 +136,135 @@ public class ObjectChecker {
/** Header "tagger " */
public static final byte[] tagger = Constants.encodeASCII("tagger "); //$NON-NLS-1$
- private final MutableObjectId tempId = new MutableObjectId();
-
- private final MutableInteger ptrout = new MutableInteger();
+ /**
+ * Potential issues identified by the checker.
+ *
+ * @since 4.2
+ */
+ public enum ErrorType {
+ // @formatter:off
+ // These names match git-core so that fsck section keys also match.
+ /***/ NULL_SHA1,
+ /***/ DUPLICATE_ENTRIES,
+ /***/ TREE_NOT_SORTED,
+ /***/ ZERO_PADDED_FILEMODE,
+ /***/ EMPTY_NAME,
+ /***/ FULL_PATHNAME,
+ /***/ HAS_DOT,
+ /***/ HAS_DOTDOT,
+ /***/ HAS_DOTGIT,
+ /***/ BAD_OBJECT_SHA1,
+ /***/ BAD_PARENT_SHA1,
+ /***/ BAD_TREE_SHA1,
+ /***/ MISSING_AUTHOR,
+ /***/ MISSING_COMMITTER,
+ /***/ MISSING_OBJECT,
+ /***/ MISSING_TREE,
+ /***/ MISSING_TYPE_ENTRY,
+ /***/ MISSING_TAG_ENTRY,
+ /***/ BAD_DATE,
+ /***/ BAD_EMAIL,
+ /***/ BAD_TIMEZONE,
+ /***/ MISSING_EMAIL,
+ /***/ MISSING_SPACE_BEFORE_DATE,
+ /***/ UNKNOWN_TYPE,
+
+ // These are unique to JGit.
+ /***/ WIN32_BAD_NAME,
+ /***/ BAD_UTF8;
+ // @formatter:on
+
+ /** @return camelCaseVersion of the name. */
+ public String getMessageId() {
+ String n = name();
+ StringBuilder r = new StringBuilder(n.length());
+ for (int i = 0; i < n.length(); i++) {
+ char c = n.charAt(i);
+ if (c != '_') {
+ r.append(StringUtils.toLowerCase(c));
+ } else {
+ r.append(n.charAt(++i));
+ }
+ }
+ return r.toString();
+ }
+ }
- private boolean allowZeroMode;
+ private final MutableObjectId tempId = new MutableObjectId();
+ private final MutableInteger bufPtr = new MutableInteger();
+ private EnumSet<ErrorType> errors = EnumSet.allOf(ErrorType.class);
+ private ObjectIdSet skipList;
private boolean allowInvalidPersonIdent;
private boolean windows;
private boolean macosx;
/**
+ * Enable accepting specific malformed (but not horribly broken) objects.
+ *
+ * @param objects
+ * collection of object names known to be broken in a non-fatal
+ * way that should be ignored by the checker.
+ * @return {@code this}
+ * @since 4.2
+ */
+ public ObjectChecker setSkipList(@Nullable ObjectIdSet objects) {
+ skipList = objects;
+ return this;
+ }
+
+ /**
+ * Configure error types to be ignored across all objects.
+ *
+ * @param ids
+ * error types to ignore. The caller's set is copied.
+ * @return {@code this}
+ * @since 4.2
+ */
+ public ObjectChecker setIgnore(@Nullable Set<ErrorType> ids) {
+ errors = EnumSet.allOf(ErrorType.class);
+ if (ids != null) {
+ errors.removeAll(ids);
+ }
+ return this;
+ }
+
+ /**
+ * Add message type to be ignored across all objects.
+ *
+ * @param id
+ * error type to ignore.
+ * @param ignore
+ * true to ignore this error; false to treat the error as an
+ * error and throw.
+ * @return {@code this}
+ * @since 4.2
+ */
+ public ObjectChecker setIgnore(ErrorType id, boolean ignore) {
+ if (ignore) {
+ errors.remove(id);
+ } else {
+ errors.add(id);
+ }
+ return this;
+ }
+
+ /**
* Enable accepting leading zero mode in tree entries.
* <p>
* Some broken Git libraries generated leading zeros in the mode part of
* tree entries. This is technically incorrect but gracefully allowed by
* git-core. JGit rejects such trees by default, but may need to accept
* them on broken histories.
+ * <p>
+ * Same as {@code setIgnore(ZERO_PADDED_FILEMODE, allow)}.
*
* @param allow allow leading zero mode.
* @return {@code this}.
* @since 3.4
*/
public ObjectChecker setAllowLeadingZeroFileMode(boolean allow) {
- allowZeroMode = allow;
- return this;
+ return setIgnore(ZERO_PADDED_FILEMODE, allow);
}
/**
@@ -184,62 +325,117 @@ public class ObjectChecker {
* @throws CorruptObjectException
* if an error is identified.
*/
- public void check(final int objType, final byte[] raw)
+ public void check(int objType, byte[] raw)
+ throws CorruptObjectException {
+ check(idFor(objType, raw), objType, raw);
+ }
+
+ /**
+ * Check an object for parsing errors.
+ *
+ * @param id
+ * identify of the object being checked.
+ * @param objType
+ * type of the object. Must be a valid object type code in
+ * {@link Constants}.
+ * @param raw
+ * the raw data which comprises the object. This should be in the
+ * canonical format (that is the format used to generate the
+ * ObjectId of the object). The array is never modified.
+ * @throws CorruptObjectException
+ * if an error is identified.
+ * @since 4.2
+ */
+ public void check(@Nullable AnyObjectId id, int objType, byte[] raw)
throws CorruptObjectException {
switch (objType) {
- case Constants.OBJ_COMMIT:
- checkCommit(raw);
+ case OBJ_COMMIT:
+ checkCommit(id, raw);
break;
- case Constants.OBJ_TAG:
- checkTag(raw);
+ case OBJ_TAG:
+ checkTag(id, raw);
break;
- case Constants.OBJ_TREE:
- checkTree(raw);
+ case OBJ_TREE:
+ checkTree(id, raw);
break;
- case Constants.OBJ_BLOB:
+ case OBJ_BLOB:
checkBlob(raw);
break;
default:
- throw new CorruptObjectException(MessageFormat.format(
+ report(UNKNOWN_TYPE, id, MessageFormat.format(
JGitText.get().corruptObjectInvalidType2,
Integer.valueOf(objType)));
}
}
- private int id(final byte[] raw, final int ptr) {
+ private boolean checkId(byte[] raw) {
+ int p = bufPtr.value;
try {
- tempId.fromString(raw, ptr);
- return ptr + Constants.OBJECT_ID_STRING_LENGTH;
+ tempId.fromString(raw, p);
} catch (IllegalArgumentException e) {
- return -1;
+ bufPtr.value = nextLF(raw, p);
+ return false;
+ }
+
+ p += OBJECT_ID_STRING_LENGTH;
+ if (raw[p] == '\n') {
+ bufPtr.value = p + 1;
+ return true;
}
+ bufPtr.value = nextLF(raw, p);
+ return false;
}
- private int personIdent(final byte[] raw, int ptr) {
- if (allowInvalidPersonIdent)
- return nextLF(raw, ptr) - 1;
+ private void checkPersonIdent(byte[] raw, @Nullable AnyObjectId id)
+ throws CorruptObjectException {
+ if (allowInvalidPersonIdent) {
+ bufPtr.value = nextLF(raw, bufPtr.value);
+ return;
+ }
- final int emailB = nextLF(raw, ptr, '<');
- if (emailB == ptr || raw[emailB - 1] != '<')
- return -1;
+ final int emailB = nextLF(raw, bufPtr.value, '<');
+ if (emailB == bufPtr.value || raw[emailB - 1] != '<') {
+ report(MISSING_EMAIL, id, JGitText.get().corruptObjectMissingEmail);
+ bufPtr.value = nextLF(raw, bufPtr.value);
+ return;
+ }
final int emailE = nextLF(raw, emailB, '>');
- if (emailE == emailB || raw[emailE - 1] != '>')
- return -1;
- if (emailE == raw.length || raw[emailE] != ' ')
- return -1;
+ if (emailE == emailB || raw[emailE - 1] != '>') {
+ report(BAD_EMAIL, id, JGitText.get().corruptObjectBadEmail);
+ bufPtr.value = nextLF(raw, bufPtr.value);
+ return;
+ }
+ if (emailE == raw.length || raw[emailE] != ' ') {
+ report(MISSING_SPACE_BEFORE_DATE, id,
+ JGitText.get().corruptObjectBadDate);
+ bufPtr.value = nextLF(raw, bufPtr.value);
+ return;
+ }
+
+ parseBase10(raw, emailE + 1, bufPtr); // when
+ if (emailE + 1 == bufPtr.value || bufPtr.value == raw.length
+ || raw[bufPtr.value] != ' ') {
+ report(BAD_DATE, id, JGitText.get().corruptObjectBadDate);
+ bufPtr.value = nextLF(raw, bufPtr.value);
+ return;
+ }
- parseBase10(raw, emailE + 1, ptrout); // when
- ptr = ptrout.value;
- if (emailE + 1 == ptr)
- return -1;
- if (ptr == raw.length || raw[ptr] != ' ')
- return -1;
+ int p = bufPtr.value + 1;
+ parseBase10(raw, p, bufPtr); // tz offset
+ if (p == bufPtr.value) {
+ report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone);
+ bufPtr.value = nextLF(raw, bufPtr.value);
+ return;
+ }
- parseBase10(raw, ptr + 1, ptrout); // tz offset
- if (ptr + 1 == ptrout.value)
- return -1;
- return ptrout.value;
+ p = bufPtr.value;
+ if (raw[p] == '\n') {
+ bufPtr.value = p + 1;
+ } else {
+ report(BAD_TIMEZONE, id, JGitText.get().corruptObjectBadTimezone);
+ bufPtr.value = nextLF(raw, p);
+ }
}
/**
@@ -250,36 +446,50 @@ public class ObjectChecker {
* @throws CorruptObjectException
* if any error was detected.
*/
- public void checkCommit(final byte[] raw) throws CorruptObjectException {
- int ptr = 0;
+ public void checkCommit(byte[] raw) throws CorruptObjectException {
+ checkCommit(idFor(OBJ_COMMIT, raw), raw);
+ }
- if ((ptr = match(raw, ptr, tree)) < 0)
- throw new CorruptObjectException(
- JGitText.get().corruptObjectNotreeHeader);
- if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n')
- throw new CorruptObjectException(
- JGitText.get().corruptObjectInvalidTree);
+ /**
+ * Check a commit for errors.
+ *
+ * @param id
+ * identity of the object being checked.
+ * @param raw
+ * the commit data. The array is never modified.
+ * @throws CorruptObjectException
+ * if any error was detected.
+ * @since 4.2
+ */
+ public void checkCommit(@Nullable AnyObjectId id, byte[] raw)
+ throws CorruptObjectException {
+ bufPtr.value = 0;
- while (match(raw, ptr, parent) >= 0) {
- ptr += parent.length;
- if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n')
- throw new CorruptObjectException(
+ if (!match(raw, tree)) {
+ report(MISSING_TREE, id, JGitText.get().corruptObjectNotreeHeader);
+ } else if (!checkId(raw)) {
+ report(BAD_TREE_SHA1, id, JGitText.get().corruptObjectInvalidTree);
+ }
+
+ while (match(raw, parent)) {
+ if (!checkId(raw)) {
+ report(BAD_PARENT_SHA1, id,
JGitText.get().corruptObjectInvalidParent);
+ }
}
- if ((ptr = match(raw, ptr, author)) < 0)
- throw new CorruptObjectException(
- JGitText.get().corruptObjectNoAuthor);
- if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n')
- throw new CorruptObjectException(
- JGitText.get().corruptObjectInvalidAuthor);
+ if (match(raw, author)) {
+ checkPersonIdent(raw, id);
+ } else {
+ report(MISSING_AUTHOR, id, JGitText.get().corruptObjectNoAuthor);
+ }
- if ((ptr = match(raw, ptr, committer)) < 0)
- throw new CorruptObjectException(
+ if (match(raw, committer)) {
+ checkPersonIdent(raw, id);
+ } else {
+ report(MISSING_COMMITTER, id,
JGitText.get().corruptObjectNoCommitter);
- if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n')
- throw new CorruptObjectException(
- JGitText.get().corruptObjectInvalidCommitter);
+ }
}
/**
@@ -290,50 +500,47 @@ public class ObjectChecker {
* @throws CorruptObjectException
* if any error was detected.
*/
- public void checkTag(final byte[] raw) throws CorruptObjectException {
- int ptr = 0;
+ public void checkTag(byte[] raw) throws CorruptObjectException {
+ checkTag(idFor(OBJ_TAG, raw), raw);
+ }
- if ((ptr = match(raw, ptr, object)) < 0)
- throw new CorruptObjectException(
+ /**
+ * Check an annotated tag for errors.
+ *
+ * @param id
+ * identity of the object being checked.
+ * @param raw
+ * the tag data. The array is never modified.
+ * @throws CorruptObjectException
+ * if any error was detected.
+ * @since 4.2
+ */
+ public void checkTag(@Nullable AnyObjectId id, byte[] raw)
+ throws CorruptObjectException {
+ bufPtr.value = 0;
+ if (!match(raw, object)) {
+ report(MISSING_OBJECT, id,
JGitText.get().corruptObjectNoObjectHeader);
- if ((ptr = id(raw, ptr)) < 0 || raw[ptr++] != '\n')
- throw new CorruptObjectException(
+ } else if (!checkId(raw)) {
+ report(BAD_OBJECT_SHA1, id,
JGitText.get().corruptObjectInvalidObject);
+ }
- if ((ptr = match(raw, ptr, type)) < 0)
- throw new CorruptObjectException(
+ if (!match(raw, type)) {
+ report(MISSING_TYPE_ENTRY, id,
JGitText.get().corruptObjectNoTypeHeader);
- ptr = nextLF(raw, ptr);
+ }
+ bufPtr.value = nextLF(raw, bufPtr.value);
- if ((ptr = match(raw, ptr, tag)) < 0)
- throw new CorruptObjectException(
+ if (!match(raw, tag)) {
+ report(MISSING_TAG_ENTRY, id,
JGitText.get().corruptObjectNoTagHeader);
- ptr = nextLF(raw, ptr);
-
- if ((ptr = match(raw, ptr, tagger)) > 0) {
- if ((ptr = personIdent(raw, ptr)) < 0 || raw[ptr++] != '\n')
- throw new CorruptObjectException(
- JGitText.get().corruptObjectInvalidTagger);
}
- }
-
- private static int lastPathChar(final int mode) {
- return FileMode.TREE.equals(mode) ? '/' : '\0';
- }
+ bufPtr.value = nextLF(raw, bufPtr.value);
- private static int pathCompare(final byte[] raw, int aPos, final int aEnd,
- final int aMode, int bPos, final int bEnd, final int bMode) {
- while (aPos < aEnd && bPos < bEnd) {
- final int cmp = (raw[aPos++] & 0xff) - (raw[bPos++] & 0xff);
- if (cmp != 0)
- return cmp;
+ if (match(raw, tagger)) {
+ checkPersonIdent(raw, id);
}
-
- if (aPos < aEnd)
- return (raw[aPos] & 0xff) - lastPathChar(bMode);
- if (bPos < bEnd)
- return lastPathChar(aMode) - (raw[bPos] & 0xff);
- return 0;
}
private static boolean duplicateName(final byte[] raw,
@@ -363,8 +570,9 @@ public class ObjectChecker {
if (nextNamePos + 1 == nextPtr)
return false;
- final int cmp = pathCompare(raw, thisNamePos, thisNameEnd,
- FileMode.TREE.getBits(), nextNamePos, nextPtr - 1, nextMode);
+ int cmp = compareSameName(
+ raw, thisNamePos, thisNameEnd,
+ raw, nextNamePos, nextPtr - 1, nextMode);
if (cmp < 0)
return false;
else if (cmp == 0)
@@ -382,7 +590,23 @@ public class ObjectChecker {
* @throws CorruptObjectException
* if any error was detected.
*/
- public void checkTree(final byte[] raw) throws CorruptObjectException {
+ public void checkTree(byte[] raw) throws CorruptObjectException {
+ checkTree(idFor(OBJ_TREE, raw), raw);
+ }
+
+ /**
+ * Check a canonical formatted tree for errors.
+ *
+ * @param id
+ * identity of the object being checked.
+ * @param raw
+ * the raw tree data. The array is never modified.
+ * @throws CorruptObjectException
+ * if any error was detected.
+ * @since 4.2
+ */
+ public void checkTree(@Nullable AnyObjectId id, byte[] raw)
+ throws CorruptObjectException {
final int sz = raw.length;
int ptr = 0;
int lastNameB = 0, lastNameE = 0, lastMode = 0;
@@ -393,74 +617,90 @@ public class ObjectChecker {
while (ptr < sz) {
int thisMode = 0;
for (;;) {
- if (ptr == sz)
+ if (ptr == sz) {
throw new CorruptObjectException(
JGitText.get().corruptObjectTruncatedInMode);
+ }
final byte c = raw[ptr++];
if (' ' == c)
break;
- if (c < '0' || c > '7')
+ if (c < '0' || c > '7') {
throw new CorruptObjectException(
JGitText.get().corruptObjectInvalidModeChar);
- if (thisMode == 0 && c == '0' && !allowZeroMode)
- throw new CorruptObjectException(
+ }
+ if (thisMode == 0 && c == '0') {
+ report(ZERO_PADDED_FILEMODE, id,
JGitText.get().corruptObjectInvalidModeStartsZero);
+ }
thisMode <<= 3;
thisMode += c - '0';
}
- if (FileMode.fromBits(thisMode).getObjectType() == Constants.OBJ_BAD)
+ if (FileMode.fromBits(thisMode).getObjectType() == OBJ_BAD) {
throw new CorruptObjectException(MessageFormat.format(
JGitText.get().corruptObjectInvalidMode2,
Integer.valueOf(thisMode)));
+ }
final int thisNameB = ptr;
- ptr = scanPathSegment(raw, ptr, sz);
- if (ptr == sz || raw[ptr] != 0)
+ ptr = scanPathSegment(raw, ptr, sz, id);
+ if (ptr == sz || raw[ptr] != 0) {
throw new CorruptObjectException(
JGitText.get().corruptObjectTruncatedInName);
- checkPathSegment2(raw, thisNameB, ptr);
+ }
+ checkPathSegment2(raw, thisNameB, ptr, id);
if (normalized != null) {
- if (!normalized.add(normalize(raw, thisNameB, ptr)))
- throw new CorruptObjectException(
+ if (!normalized.add(normalize(raw, thisNameB, ptr))) {
+ report(DUPLICATE_ENTRIES, id,
JGitText.get().corruptObjectDuplicateEntryNames);
- } else if (duplicateName(raw, thisNameB, ptr))
- throw new CorruptObjectException(
+ }
+ } else if (duplicateName(raw, thisNameB, ptr)) {
+ report(DUPLICATE_ENTRIES, id,
JGitText.get().corruptObjectDuplicateEntryNames);
+ }
if (lastNameB != 0) {
- final int cmp = pathCompare(raw, lastNameB, lastNameE,
- lastMode, thisNameB, ptr, thisMode);
- if (cmp > 0)
- throw new CorruptObjectException(
+ int cmp = compare(
+ raw, lastNameB, lastNameE, lastMode,
+ raw, thisNameB, ptr, thisMode);
+ if (cmp > 0) {
+ report(TREE_NOT_SORTED, id,
JGitText.get().corruptObjectIncorrectSorting);
+ }
}
lastNameB = thisNameB;
lastNameE = ptr;
lastMode = thisMode;
- ptr += 1 + Constants.OBJECT_ID_LENGTH;
- if (ptr > sz)
+ ptr += 1 + OBJECT_ID_LENGTH;
+ if (ptr > sz) {
throw new CorruptObjectException(
JGitText.get().corruptObjectTruncatedInObjectId);
+ }
+ if (ObjectId.zeroId().compareTo(raw, ptr - OBJECT_ID_LENGTH) == 0) {
+ report(NULL_SHA1, id, JGitText.get().corruptObjectZeroId);
+ }
}
}
- private int scanPathSegment(byte[] raw, int ptr, int end)
- throws CorruptObjectException {
+ private int scanPathSegment(byte[] raw, int ptr, int end,
+ @Nullable AnyObjectId id) throws CorruptObjectException {
for (; ptr < end; ptr++) {
byte c = raw[ptr];
- if (c == 0)
+ if (c == 0) {
return ptr;
- if (c == '/')
- throw new CorruptObjectException(
+ }
+ if (c == '/') {
+ report(FULL_PATHNAME, id,
JGitText.get().corruptObjectNameContainsSlash);
+ }
if (windows && isInvalidOnWindows(c)) {
- if (c > 31)
+ if (c > 31) {
throw new CorruptObjectException(String.format(
JGitText.get().corruptObjectNameContainsChar,
Byte.valueOf(c)));
+ }
throw new CorruptObjectException(String.format(
JGitText.get().corruptObjectNameContainsByte,
Integer.valueOf(c & 0xff)));
@@ -469,6 +709,26 @@ public class ObjectChecker {
return ptr;
}
+ @SuppressWarnings("resource")
+ @Nullable
+ private ObjectId idFor(int objType, byte[] raw) {
+ if (skipList != null) {
+ return new ObjectInserter.Formatter().idFor(objType, raw);
+ }
+ return null;
+ }
+
+ private void report(@NonNull ErrorType err, @Nullable AnyObjectId id,
+ String why) throws CorruptObjectException {
+ if (errors.contains(err)
+ && (id == null || skipList == null || !skipList.contains(id))) {
+ if (id != null) {
+ throw new CorruptObjectException(err, id, why);
+ }
+ throw new CorruptObjectException(why);
+ }
+ }
+
/**
* Check tree path entry for validity.
* <p>
@@ -519,73 +779,82 @@ public class ObjectChecker {
*/
public void checkPathSegment(byte[] raw, int ptr, int end)
throws CorruptObjectException {
- int e = scanPathSegment(raw, ptr, end);
+ int e = scanPathSegment(raw, ptr, end, null);
if (e < end && raw[e] == 0)
throw new CorruptObjectException(
JGitText.get().corruptObjectNameContainsNullByte);
- checkPathSegment2(raw, ptr, end);
+ checkPathSegment2(raw, ptr, end, null);
}
- private void checkPathSegment2(byte[] raw, int ptr, int end)
- throws CorruptObjectException {
- if (ptr == end)
- throw new CorruptObjectException(
- JGitText.get().corruptObjectNameZeroLength);
+ private void checkPathSegment2(byte[] raw, int ptr, int end,
+ @Nullable AnyObjectId id) throws CorruptObjectException {
+ if (ptr == end) {
+ report(EMPTY_NAME, id, JGitText.get().corruptObjectNameZeroLength);
+ return;
+ }
+
if (raw[ptr] == '.') {
switch (end - ptr) {
case 1:
- throw new CorruptObjectException(
- JGitText.get().corruptObjectNameDot);
+ report(HAS_DOT, id, JGitText.get().corruptObjectNameDot);
+ break;
case 2:
- if (raw[ptr + 1] == '.')
- throw new CorruptObjectException(
+ if (raw[ptr + 1] == '.') {
+ report(HAS_DOTDOT, id,
JGitText.get().corruptObjectNameDotDot);
+ }
break;
case 4:
- if (isGit(raw, ptr + 1))
- throw new CorruptObjectException(String.format(
+ if (isGit(raw, ptr + 1)) {
+ report(HAS_DOTGIT, id, String.format(
JGitText.get().corruptObjectInvalidName,
RawParseUtils.decode(raw, ptr, end)));
+ }
break;
default:
- if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end))
- throw new CorruptObjectException(String.format(
+ if (end - ptr > 4 && isNormalizedGit(raw, ptr + 1, end)) {
+ report(HAS_DOTGIT, id, String.format(
JGitText.get().corruptObjectInvalidName,
RawParseUtils.decode(raw, ptr, end)));
+ }
}
} else if (isGitTilde1(raw, ptr, end)) {
- throw new CorruptObjectException(String.format(
+ report(HAS_DOTGIT, id, String.format(
JGitText.get().corruptObjectInvalidName,
RawParseUtils.decode(raw, ptr, end)));
}
-
- if (macosx && isMacHFSGit(raw, ptr, end))
- throw new CorruptObjectException(String.format(
+ if (macosx && isMacHFSGit(raw, ptr, end, id)) {
+ report(HAS_DOTGIT, id, String.format(
JGitText.get().corruptObjectInvalidNameIgnorableUnicode,
RawParseUtils.decode(raw, ptr, end)));
+ }
if (windows) {
// Windows ignores space and dot at end of file name.
- if (raw[end - 1] == ' ' || raw[end - 1] == '.')
- throw new CorruptObjectException(String.format(
+ if (raw[end - 1] == ' ' || raw[end - 1] == '.') {
+ report(WIN32_BAD_NAME, id, String.format(
JGitText.get().corruptObjectInvalidNameEnd,
Character.valueOf(((char) raw[end - 1]))));
- if (end - ptr >= 3)
- checkNotWindowsDevice(raw, ptr, end);
+ }
+ if (end - ptr >= 3) {
+ checkNotWindowsDevice(raw, ptr, end, id);
+ }
}
}
// Mac's HFS+ folds permutations of ".git" and Unicode ignorable characters
// to ".git" therefore we should prevent such names
- private static boolean isMacHFSGit(byte[] raw, int ptr, int end)
- throws CorruptObjectException {
+ private boolean isMacHFSGit(byte[] raw, int ptr, int end,
+ @Nullable AnyObjectId id) throws CorruptObjectException {
boolean ignorable = false;
byte[] git = new byte[] { '.', 'g', 'i', 't' };
int g = 0;
while (ptr < end) {
switch (raw[ptr]) {
case (byte) 0xe2: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=8192
- checkTruncatedIgnorableUTF8(raw, ptr, end);
+ if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) {
+ return false;
+ }
switch (raw[ptr + 1]) {
case (byte) 0x80:
switch (raw[ptr + 2]) {
@@ -622,7 +891,9 @@ public class ObjectChecker {
return false;
}
case (byte) 0xef: // http://www.utf8-chartable.de/unicode-utf8-table.pl?start=65024
- checkTruncatedIgnorableUTF8(raw, ptr, end);
+ if (!checkTruncatedIgnorableUTF8(raw, ptr, end, id)) {
+ return false;
+ }
// U+FEFF 0xefbbbf ZERO WIDTH NO-BREAK SPACE
if ((raw[ptr + 1] == (byte) 0xbb)
&& (raw[ptr + 2] == (byte) 0xbf)) {
@@ -643,12 +914,15 @@ public class ObjectChecker {
return false;
}
- private static void checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end)
- throws CorruptObjectException {
- if ((ptr + 2) >= end)
- throw new CorruptObjectException(MessageFormat.format(
+ private boolean checkTruncatedIgnorableUTF8(byte[] raw, int ptr, int end,
+ @Nullable AnyObjectId id) throws CorruptObjectException {
+ if ((ptr + 2) >= end) {
+ report(BAD_UTF8, id, MessageFormat.format(
JGitText.get().corruptObjectInvalidNameInvalidUtf8,
toHexString(raw, ptr, end)));
+ return false;
+ }
+ return true;
}
private static String toHexString(byte[] raw, int ptr, int end) {
@@ -658,33 +932,36 @@ public class ObjectChecker {
return b.toString();
}
- private static void checkNotWindowsDevice(byte[] raw, int ptr, int end)
- throws CorruptObjectException {
+ private void checkNotWindowsDevice(byte[] raw, int ptr, int end,
+ @Nullable AnyObjectId id) throws CorruptObjectException {
switch (toLower(raw[ptr])) {
case 'a': // AUX
if (end - ptr >= 3
&& toLower(raw[ptr + 1]) == 'u'
&& toLower(raw[ptr + 2]) == 'x'
- && (end - ptr == 3 || raw[ptr + 3] == '.'))
- throw new CorruptObjectException(
+ && (end - ptr == 3 || raw[ptr + 3] == '.')) {
+ report(WIN32_BAD_NAME, id,
JGitText.get().corruptObjectInvalidNameAux);
+ }
break;
case 'c': // CON, COM[1-9]
if (end - ptr >= 3
&& toLower(raw[ptr + 2]) == 'n'
&& toLower(raw[ptr + 1]) == 'o'
- && (end - ptr == 3 || raw[ptr + 3] == '.'))
- throw new CorruptObjectException(
+ && (end - ptr == 3 || raw[ptr + 3] == '.')) {
+ report(WIN32_BAD_NAME, id,
JGitText.get().corruptObjectInvalidNameCon);
+ }
if (end - ptr >= 4
&& toLower(raw[ptr + 2]) == 'm'
&& toLower(raw[ptr + 1]) == 'o'
&& isPositiveDigit(raw[ptr + 3])
- && (end - ptr == 4 || raw[ptr + 4] == '.'))
- throw new CorruptObjectException(String.format(
+ && (end - ptr == 4 || raw[ptr + 4] == '.')) {
+ report(WIN32_BAD_NAME, id, String.format(
JGitText.get().corruptObjectInvalidNameCom,
Character.valueOf(((char) raw[ptr + 3]))));
+ }
break;
case 'l': // LPT[1-9]
@@ -692,28 +969,31 @@ public class ObjectChecker {
&& toLower(raw[ptr + 1]) == 'p'
&& toLower(raw[ptr + 2]) == 't'
&& isPositiveDigit(raw[ptr + 3])
- && (end - ptr == 4 || raw[ptr + 4] == '.'))
- throw new CorruptObjectException(String.format(
+ && (end - ptr == 4 || raw[ptr + 4] == '.')) {
+ report(WIN32_BAD_NAME, id, String.format(
JGitText.get().corruptObjectInvalidNameLpt,
Character.valueOf(((char) raw[ptr + 3]))));
+ }
break;
case 'n': // NUL
if (end - ptr >= 3
&& toLower(raw[ptr + 1]) == 'u'
&& toLower(raw[ptr + 2]) == 'l'
- && (end - ptr == 3 || raw[ptr + 3] == '.'))
- throw new CorruptObjectException(
+ && (end - ptr == 3 || raw[ptr + 3] == '.')) {
+ report(WIN32_BAD_NAME, id,
JGitText.get().corruptObjectInvalidNameNul);
+ }
break;
case 'p': // PRN
if (end - ptr >= 3
&& toLower(raw[ptr + 1]) == 'r'
&& toLower(raw[ptr + 2]) == 'n'
- && (end - ptr == 3 || raw[ptr + 3] == '.'))
- throw new CorruptObjectException(
+ && (end - ptr == 3 || raw[ptr + 3] == '.')) {
+ report(WIN32_BAD_NAME, id,
JGitText.get().corruptObjectInvalidNamePrn);
+ }
break;
}
}
@@ -766,6 +1046,15 @@ public class ObjectChecker {
return false;
}
+ private boolean match(byte[] b, byte[] src) {
+ int r = RawParseUtils.match(b, bufPtr.value, src);
+ if (r < 0) {
+ return false;
+ }
+ bufPtr.value = r;
+ return true;
+ }
+
private static char toLower(byte b) {
if ('A' <= b && b <= 'Z')
return (char) (b + ('a' - 'A'));
@@ -790,58 +1079,6 @@ public class ObjectChecker {
private String normalize(byte[] raw, int ptr, int end) {
String n = RawParseUtils.decode(raw, ptr, end).toLowerCase(Locale.US);
- return macosx ? Normalizer.normalize(n) : n;
- }
-
- private static class Normalizer {
- // TODO Simplify invocation to Normalizer after dropping Java 5.
- private static final Method normalize;
- private static final Object nfc;
- static {
- Method method;
- Object formNfc;
- try {
- Class<?> formClazz = Class.forName("java.text.Normalizer$Form"); //$NON-NLS-1$
- formNfc = formClazz.getField("NFC").get(null); //$NON-NLS-1$
- method = Class.forName("java.text.Normalizer") //$NON-NLS-1$
- .getMethod("normalize", CharSequence.class, formClazz); //$NON-NLS-1$
- } catch (ClassNotFoundException e) {
- method = null;
- formNfc = null;
- } catch (NoSuchFieldException e) {
- method = null;
- formNfc = null;
- } catch (NoSuchMethodException e) {
- method = null;
- formNfc = null;
- } catch (SecurityException e) {
- method = null;
- formNfc = null;
- } catch (IllegalArgumentException e) {
- method = null;
- formNfc = null;
- } catch (IllegalAccessException e) {
- method = null;
- formNfc = null;
- }
- normalize = method;
- nfc = formNfc;
- }
-
- static String normalize(String in) {
- if (normalize == null)
- return in;
- try {
- return (String) normalize.invoke(null, in, nfc);
- } catch (IllegalAccessException e) {
- return in;
- } catch (InvocationTargetException e) {
- if (e.getCause() instanceof RuntimeException)
- throw (RuntimeException) e.getCause();
- if (e.getCause() instanceof Error)
- throw (Error) e.getCause();
- return in;
- }
- }
+ return macosx ? Normalizer.normalize(n, Normalizer.Form.NFC) : n;
}
}
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 95b16d9176..442261cbd5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdOwnerMap.java
@@ -67,8 +67,8 @@ import java.util.NoSuchElementException;
* @param <V>
* type of subclass of ObjectId that will be stored in the map.
*/
-public class ObjectIdOwnerMap<V extends ObjectIdOwnerMap.Entry> implements
- Iterable<V> {
+public class ObjectIdOwnerMap<V extends ObjectIdOwnerMap.Entry>
+ implements Iterable<V>, ObjectIdSet {
/** Size of the initial directory, will grow as necessary. */
private static final int INITIAL_DIRECTORY = 1024;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
index f481c772dc..c286f5e463 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdRef.java
@@ -44,6 +44,9 @@
package org.eclipse.jgit.lib;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+
/** A {@link Ref} that points directly at an {@link ObjectId}. */
public abstract class ObjectIdRef implements Ref {
/** Any reference whose peeled value is not yet known. */
@@ -56,13 +59,15 @@ public abstract class ObjectIdRef implements Ref {
* @param name
* name of this ref.
* @param id
- * current value of the ref. May be null to indicate a ref
- * that does not exist yet.
+ * current value of the ref. May be {@code null} to indicate
+ * a ref that does not exist yet.
*/
- public Unpeeled(Storage st, String name, ObjectId id) {
+ public Unpeeled(@NonNull Storage st, @NonNull String name,
+ @Nullable ObjectId id) {
super(st, name, id);
}
+ @Nullable
public ObjectId getPeeledObjectId() {
return null;
}
@@ -88,11 +93,13 @@ public abstract class ObjectIdRef implements Ref {
* @param p
* the first non-tag object that tag {@code id} points to.
*/
- public PeeledTag(Storage st, String name, ObjectId id, ObjectId p) {
+ public PeeledTag(@NonNull Storage st, @NonNull String name,
+ @Nullable ObjectId id, @NonNull ObjectId p) {
super(st, name, id);
peeledObjectId = p;
}
+ @NonNull
public ObjectId getPeeledObjectId() {
return peeledObjectId;
}
@@ -112,13 +119,15 @@ public abstract class ObjectIdRef implements Ref {
* @param name
* name of this ref.
* @param id
- * current value of the ref. May be null to indicate a ref
- * that does not exist yet.
+ * current value of the ref. May be {@code null} to indicate
+ * a ref that does not exist yet.
*/
- public PeeledNonTag(Storage st, String name, ObjectId id) {
+ public PeeledNonTag(@NonNull Storage st, @NonNull String name,
+ @Nullable ObjectId id) {
super(st, name, id);
}
+ @Nullable
public ObjectId getPeeledObjectId() {
return null;
}
@@ -142,15 +151,17 @@ public abstract class ObjectIdRef implements Ref {
* @param name
* name of this ref.
* @param id
- * current value of the ref. May be null to indicate a ref that
- * does not exist yet.
+ * current value of the ref. May be {@code null} to indicate a
+ * ref that does not exist yet.
*/
- protected ObjectIdRef(Storage st, String name, ObjectId id) {
+ protected ObjectIdRef(@NonNull Storage st, @NonNull String name,
+ @Nullable ObjectId id) {
this.name = name;
this.storage = st;
this.objectId = id;
}
+ @NonNull
public String getName() {
return name;
}
@@ -159,22 +170,27 @@ public abstract class ObjectIdRef implements Ref {
return false;
}
+ @NonNull
public Ref getLeaf() {
return this;
}
+ @NonNull
public Ref getTarget() {
return this;
}
+ @Nullable
public ObjectId getObjectId() {
return objectId;
}
+ @NonNull
public Storage getStorage() {
return storage;
}
+ @NonNull
@Override
public String toString() {
StringBuilder r = new StringBuilder();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSet.java
index c7e41bce04..0b5848463c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymlinkTreeEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSet.java
@@ -1,6 +1,5 @@
/*
- * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
+ * Copyright (C) 2015, Google Inc.
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -45,41 +44,21 @@
package org.eclipse.jgit.lib;
/**
- * A tree entry representing a symbolic link.
+ * Simple set of ObjectIds.
+ * <p>
+ * Usually backed by a read-only data structure such as
+ * {@link org.eclipse.jgit.internal.storage.file.PackIndex}. Mutable types like
+ * {@link ObjectIdOwnerMap} also implement the interface by checking keys.
*
- * Note. Java cannot really handle these as file system objects.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
+ * @since 4.2
*/
-@Deprecated
-public class SymlinkTreeEntry extends TreeEntry {
-
+public interface ObjectIdSet {
/**
- * Construct a {@link SymlinkTreeEntry} with the specified name and SHA-1 in
- * the specified parent
+ * Returns true if the objectId is contained within the collection.
*
- * @param parent
- * @param id
- * @param nameUTF8
+ * @param objectId
+ * the objectId to find
+ * @return whether the collection contains the objectId.
*/
- public SymlinkTreeEntry(final Tree parent, final ObjectId id,
- final byte[] nameUTF8) {
- super(parent, id, nameUTF8);
- }
-
- public FileMode getMode() {
- return FileMode.SYMLINK;
- }
-
- public String toString() {
- final StringBuilder r = new StringBuilder();
- r.append(ObjectId.toString(getId()));
- r.append(" S "); //$NON-NLS-1$
- r.append(getFullName());
- return r.toString();
- }
+ boolean contains(AnyObjectId objectId);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
index 48aa109e7c..faed64bfe7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectIdSubclassMap.java
@@ -60,7 +60,8 @@ import java.util.NoSuchElementException;
* @param <V>
* type of subclass of ObjectId that will be stored in the map.
*/
-public class ObjectIdSubclassMap<V extends ObjectId> implements Iterable<V> {
+public class ObjectIdSubclassMap<V extends ObjectId>
+ implements Iterable<V>, ObjectIdSet {
private static final int INITIAL_TABLE_SIZE = 2048;
int size;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
index f119c44fe2..a78a90fe58 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Ref.java
@@ -43,6 +43,9 @@
package org.eclipse.jgit.lib;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+
/**
* Pairing of a name and the {@link ObjectId} it currently has.
* <p>
@@ -126,6 +129,7 @@ public interface Ref {
*
* @return name of this ref.
*/
+ @NonNull
public String getName();
/**
@@ -156,6 +160,7 @@ public interface Ref {
*
* @return the reference that actually stores the ObjectId value.
*/
+ @NonNull
public abstract Ref getLeaf();
/**
@@ -170,22 +175,27 @@ public interface Ref {
*
* @return the target reference, or {@code this}.
*/
+ @NonNull
public abstract Ref getTarget();
/**
* Cached value of this ref.
*
- * @return the value of this ref at the last time we read it.
+ * @return the value of this ref at the last time we read it. May be
+ * {@code null} to indicate a ref that does not exist yet or a
+ * symbolic ref pointing to an unborn branch.
*/
+ @Nullable
public abstract ObjectId getObjectId();
/**
* Cached value of <code>ref^{}</code> (the ref peeled to commit).
*
* @return if this ref is an annotated tag the id of the commit (or tree or
- * blob) that the annotated tag refers to; null if this ref does not
- * refer to an annotated tag.
+ * blob) that the annotated tag refers to; {@code null} if this ref
+ * does not refer to an annotated tag.
*/
+ @Nullable
public abstract ObjectId getPeeledObjectId();
/**
@@ -201,5 +211,6 @@ public interface Ref {
*
* @return type of ref.
*/
+ @NonNull
public abstract Storage getStorage();
}
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 986666f2f4..c0c3862c8b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -82,8 +82,10 @@ public abstract class RefDatabase {
* <p>
* If the reference is nested deeper than this depth, the implementation
* should either fail, or at least claim the reference does not exist.
+ *
+ * @since 4.2
*/
- protected static final int MAX_SYMBOLIC_REF_DEPTH = 5;
+ public static final int MAX_SYMBOLIC_REF_DEPTH = 5;
/** Magic value for {@link #getRefs(String)} to return all references. */
public static final String ALL = "";//$NON-NLS-1$
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
index 747fa62b50..3a02b22813 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefWriter.java
@@ -119,13 +119,20 @@ public abstract class RefWriter {
continue;
}
- r.getObjectId().copyTo(tmp, w);
+ ObjectId objectId = r.getObjectId();
+ if (objectId == null) {
+ // Symrefs to unborn branches aren't advertised in the info/refs
+ // file.
+ continue;
+ }
+ objectId.copyTo(tmp, w);
w.write('\t');
w.write(r.getName());
w.write('\n');
- if (r.getPeeledObjectId() != null) {
- r.getPeeledObjectId().copyTo(tmp, w);
+ ObjectId peeledObjectId = r.getPeeledObjectId();
+ if (peeledObjectId != null) {
+ peeledObjectId.copyTo(tmp, w);
w.write('\t');
w.write(r.getName());
w.write("^{}\n"); //$NON-NLS-1$
@@ -167,14 +174,21 @@ public abstract class RefWriter {
if (r.getStorage() != Ref.Storage.PACKED)
continue;
- r.getObjectId().copyTo(tmp, w);
+ ObjectId objectId = r.getObjectId();
+ if (objectId == null) {
+ // A packed ref cannot be a symref, let alone a symref
+ // to an unborn branch.
+ throw new NullPointerException();
+ }
+ objectId.copyTo(tmp, w);
w.write(' ');
w.write(r.getName());
w.write('\n');
- if (r.getPeeledObjectId() != null) {
+ ObjectId peeledObjectId = r.getPeeledObjectId();
+ if (peeledObjectId != null) {
w.write('^');
- r.getPeeledObjectId().copyTo(tmp, w);
+ peeledObjectId.copyTo(tmp, w);
w.write('\n');
}
}
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 49a970d03a..f8266133a6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -911,12 +911,16 @@ public abstract class Repository implements AutoCloseable {
@Nullable
public String getFullBranch() throws IOException {
Ref head = getRef(Constants.HEAD);
- if (head == null)
+ if (head == null) {
return null;
- if (head.isSymbolic())
+ }
+ if (head.isSymbolic()) {
return head.getTarget().getName();
- if (head.getObjectId() != null)
- return head.getObjectId().name();
+ }
+ ObjectId objectId = head.getObjectId();
+ if (objectId != null) {
+ return objectId.name();
+ }
return null;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
index 43b1510f94..eeab921a7a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/SymbolicRef.java
@@ -43,6 +43,9 @@
package org.eclipse.jgit.lib;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+
/**
* A reference that indirectly points at another {@link Ref}.
* <p>
@@ -62,11 +65,12 @@ public class SymbolicRef implements Ref {
* @param target
* the ref we reference and derive our value from.
*/
- public SymbolicRef(String refName, Ref target) {
+ public SymbolicRef(@NonNull String refName, @NonNull Ref target) {
this.name = refName;
this.target = target;
}
+ @NonNull
public String getName() {
return name;
}
@@ -75,6 +79,7 @@ public class SymbolicRef implements Ref {
return true;
}
+ @NonNull
public Ref getLeaf() {
Ref dst = getTarget();
while (dst.isSymbolic())
@@ -82,18 +87,22 @@ public class SymbolicRef implements Ref {
return dst;
}
+ @NonNull
public Ref getTarget() {
return target;
}
+ @Nullable
public ObjectId getObjectId() {
return getLeaf().getObjectId();
}
+ @NonNull
public Storage getStorage() {
return Storage.LOOSE;
}
+ @Nullable
public ObjectId getPeeledObjectId() {
return getLeaf().getPeeledObjectId();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java
deleted file mode 100644
index 43bd489dc0..0000000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Tree.java
+++ /dev/null
@@ -1,601 +0,0 @@
-/*
- * Copyright (C) 2007, Robin Rosenberg <me@lathund.dewire.com>
- * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-import java.text.MessageFormat;
-
-import org.eclipse.jgit.errors.CorruptObjectException;
-import org.eclipse.jgit.errors.EntryExistsException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.errors.ObjectWritingException;
-import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.util.RawParseUtils;
-
-/**
- * A representation of a Git tree entry. A Tree is a directory in Git.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
- */
-@Deprecated
-public class Tree extends TreeEntry {
- private static final TreeEntry[] EMPTY_TREE = {};
-
- /**
- * Compare two names represented as bytes. Since git treats names of trees and
- * blobs differently we have one parameter that represents a '/' for trees. For
- * other objects the value should be NUL. The names are compare by their positive
- * byte value (0..255).
- *
- * A blob and a tree with the same name will not compare equal.
- *
- * @param a name
- * @param b name
- * @param lasta '/' if a is a tree, else NUL
- * @param lastb '/' if b is a tree, else NUL
- *
- * @return &lt; 0 if a is sorted before b, 0 if they are the same, else b
- */
- public static final int compareNames(final byte[] a, final byte[] b, final int lasta,final int lastb) {
- return compareNames(a, b, 0, b.length, lasta, lastb);
- }
-
- private static final int compareNames(final byte[] a, final byte[] nameUTF8,
- final int nameStart, final int nameEnd, final int lasta, int lastb) {
- int j,k;
- for (j = 0, k = nameStart; j < a.length && k < nameEnd; j++, k++) {
- final int aj = a[j] & 0xff;
- final int bk = nameUTF8[k] & 0xff;
- if (aj < bk)
- return -1;
- else if (aj > bk)
- return 1;
- }
- if (j < a.length) {
- int aj = a[j]&0xff;
- if (aj < lastb)
- return -1;
- else if (aj > lastb)
- return 1;
- else
- if (j == a.length - 1)
- return 0;
- else
- return -1;
- }
- if (k < nameEnd) {
- int bk = nameUTF8[k] & 0xff;
- if (lasta < bk)
- return -1;
- else if (lasta > bk)
- return 1;
- else
- if (k == nameEnd - 1)
- return 0;
- else
- return 1;
- }
- if (lasta < lastb)
- return -1;
- else if (lasta > lastb)
- return 1;
-
- final int namelength = nameEnd - nameStart;
- if (a.length == namelength)
- return 0;
- else if (a.length < namelength)
- return -1;
- else
- return 1;
- }
-
- private static final byte[] substring(final byte[] s, final int nameStart,
- final int nameEnd) {
- if (nameStart == 0 && nameStart == s.length)
- return s;
- final byte[] n = new byte[nameEnd - nameStart];
- System.arraycopy(s, nameStart, n, 0, n.length);
- return n;
- }
-
- private static final int binarySearch(final TreeEntry[] entries,
- final byte[] nameUTF8, final int nameUTF8last, final int nameStart, final int nameEnd) {
- if (entries.length == 0)
- return -1;
- int high = entries.length;
- int low = 0;
- do {
- final int mid = (low + high) >>> 1;
- final int cmp = compareNames(entries[mid].getNameUTF8(), nameUTF8,
- nameStart, nameEnd, TreeEntry.lastChar(entries[mid]), nameUTF8last);
- if (cmp < 0)
- low = mid + 1;
- else if (cmp == 0)
- return mid;
- else
- high = mid;
- } while (low < high);
- return -(low + 1);
- }
-
- private final Repository db;
-
- private TreeEntry[] contents;
-
- /**
- * Constructor for a new Tree
- *
- * @param repo The repository that owns the Tree.
- */
- public Tree(final Repository repo) {
- super(null, null, null);
- db = repo;
- contents = EMPTY_TREE;
- }
-
- /**
- * Construct a Tree object with known content and hash value
- *
- * @param repo
- * @param myId
- * @param raw
- * @throws IOException
- */
- public Tree(final Repository repo, final ObjectId myId, final byte[] raw)
- throws IOException {
- super(null, myId, null);
- db = repo;
- readTree(raw);
- }
-
- /**
- * Construct a new Tree under another Tree
- *
- * @param parent
- * @param nameUTF8
- */
- public Tree(final Tree parent, final byte[] nameUTF8) {
- super(parent, null, nameUTF8);
- db = parent.getRepository();
- contents = EMPTY_TREE;
- }
-
- /**
- * Construct a Tree with a known SHA-1 under another tree. Data is not yet
- * specified and will have to be loaded on demand.
- *
- * @param parent
- * @param id
- * @param nameUTF8
- */
- public Tree(final Tree parent, final ObjectId id, final byte[] nameUTF8) {
- super(parent, id, nameUTF8);
- db = parent.getRepository();
- }
-
- public FileMode getMode() {
- return FileMode.TREE;
- }
-
- /**
- * @return true if this Tree is the top level Tree.
- */
- public boolean isRoot() {
- return getParent() == null;
- }
-
- public Repository getRepository() {
- return db;
- }
-
- /**
- * @return true of the data of this Tree is loaded
- */
- public boolean isLoaded() {
- return contents != null;
- }
-
- /**
- * Forget the in-memory data for this tree.
- */
- public void unload() {
- if (isModified())
- throw new IllegalStateException(JGitText.get().cannotUnloadAModifiedTree);
- contents = null;
- }
-
- /**
- * Adds a new or existing file with the specified name to this tree.
- * Trees are added if necessary as the name may contain '/':s.
- *
- * @param name Name
- * @return a {@link FileTreeEntry} for the added file.
- * @throws IOException
- */
- public FileTreeEntry addFile(final String name) throws IOException {
- return addFile(Repository.gitInternalSlash(Constants.encode(name)), 0);
- }
-
- /**
- * Adds a new or existing file with the specified name to this tree.
- * Trees are added if necessary as the name may contain '/':s.
- *
- * @param s an array containing the name
- * @param offset when the name starts in the tree.
- *
- * @return a {@link FileTreeEntry} for the added file.
- * @throws IOException
- */
- public FileTreeEntry addFile(final byte[] s, final int offset)
- throws IOException {
- int slash;
- int p;
-
- for (slash = offset; slash < s.length && s[slash] != '/'; slash++) {
- // search for path component terminator
- }
-
- ensureLoaded();
- byte xlast = slash<s.length ? (byte)'/' : 0;
- p = binarySearch(contents, s, xlast, offset, slash);
- if (p >= 0 && slash < s.length && contents[p] instanceof Tree)
- return ((Tree) contents[p]).addFile(s, slash + 1);
-
- final byte[] newName = substring(s, offset, slash);
- if (p >= 0)
- throw new EntryExistsException(RawParseUtils.decode(newName));
- else if (slash < s.length) {
- final Tree t = new Tree(this, newName);
- insertEntry(p, t);
- return t.addFile(s, slash + 1);
- } else {
- final FileTreeEntry f = new FileTreeEntry(this, null, newName,
- false);
- insertEntry(p, f);
- return f;
- }
- }
-
- /**
- * Adds a new or existing Tree with the specified name to this tree.
- * Trees are added if necessary as the name may contain '/':s.
- *
- * @param name Name
- * @return a {@link FileTreeEntry} for the added tree.
- * @throws IOException
- */
- public Tree addTree(final String name) throws IOException {
- return addTree(Repository.gitInternalSlash(Constants.encode(name)), 0);
- }
-
- /**
- * Adds a new or existing Tree with the specified name to this tree.
- * Trees are added if necessary as the name may contain '/':s.
- *
- * @param s an array containing the name
- * @param offset when the name starts in the tree.
- *
- * @return a {@link FileTreeEntry} for the added tree.
- * @throws IOException
- */
- public Tree addTree(final byte[] s, final int offset) throws IOException {
- int slash;
- int p;
-
- for (slash = offset; slash < s.length && s[slash] != '/'; slash++) {
- // search for path component terminator
- }
-
- ensureLoaded();
- p = binarySearch(contents, s, (byte)'/', offset, slash);
- if (p >= 0 && slash < s.length && contents[p] instanceof Tree)
- return ((Tree) contents[p]).addTree(s, slash + 1);
-
- final byte[] newName = substring(s, offset, slash);
- if (p >= 0)
- throw new EntryExistsException(RawParseUtils.decode(newName));
-
- final Tree t = new Tree(this, newName);
- insertEntry(p, t);
- return slash == s.length ? t : t.addTree(s, slash + 1);
- }
-
- /**
- * Add the specified tree entry to this tree.
- *
- * @param e
- * @throws IOException
- */
- public void addEntry(final TreeEntry e) throws IOException {
- final int p;
-
- ensureLoaded();
- p = binarySearch(contents, e.getNameUTF8(), TreeEntry.lastChar(e), 0, e.getNameUTF8().length);
- if (p < 0) {
- e.attachParent(this);
- insertEntry(p, e);
- } else {
- throw new EntryExistsException(e.getName());
- }
- }
-
- private void insertEntry(int p, final TreeEntry e) {
- final TreeEntry[] c = contents;
- final TreeEntry[] n = new TreeEntry[c.length + 1];
- p = -(p + 1);
- for (int k = c.length - 1; k >= p; k--)
- n[k + 1] = c[k];
- n[p] = e;
- for (int k = p - 1; k >= 0; k--)
- n[k] = c[k];
- contents = n;
- setModified();
- }
-
- void removeEntry(final TreeEntry e) {
- final TreeEntry[] c = contents;
- final int p = binarySearch(c, e.getNameUTF8(), TreeEntry.lastChar(e), 0,
- e.getNameUTF8().length);
- if (p >= 0) {
- final TreeEntry[] n = new TreeEntry[c.length - 1];
- for (int k = c.length - 1; k > p; k--)
- n[k - 1] = c[k];
- for (int k = p - 1; k >= 0; k--)
- n[k] = c[k];
- contents = n;
- setModified();
- }
- }
-
- /**
- * @return number of members in this tree
- * @throws IOException
- */
- public int memberCount() throws IOException {
- ensureLoaded();
- return contents.length;
- }
-
- /**
- * Return all members of the tree sorted in Git order.
- *
- * Entries are sorted by the numerical unsigned byte
- * values with (sub)trees having an implicit '/'. An
- * example of a tree with three entries. a:b is an
- * actual file name here.
- *
- * <p>
- * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a.b
- * 040000 tree 4277b6e69d25e5efa77c455340557b384a4c018a a
- * 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a:b
- *
- * @return all entries in this Tree, sorted.
- * @throws IOException
- */
- public TreeEntry[] members() throws IOException {
- ensureLoaded();
- final TreeEntry[] c = contents;
- if (c.length != 0) {
- final TreeEntry[] r = new TreeEntry[c.length];
- for (int k = c.length - 1; k >= 0; k--)
- r[k] = c[k];
- return r;
- } else
- return c;
- }
-
- private boolean exists(final String s, byte slast) throws IOException {
- return findMember(s, slast) != null;
- }
-
- /**
- * @param path to the tree.
- * @return true if a tree with the specified path can be found under this
- * tree.
- * @throws IOException
- */
- public boolean existsTree(String path) throws IOException {
- return exists(path,(byte)'/');
- }
-
- /**
- * @param path of the non-tree entry.
- * @return true if a blob, symlink, or gitlink with the specified name
- * can be found under this tree.
- * @throws IOException
- */
- public boolean existsBlob(String path) throws IOException {
- return exists(path,(byte)0);
- }
-
- private TreeEntry findMember(final String s, byte slast) throws IOException {
- return findMember(Repository.gitInternalSlash(Constants.encode(s)), slast, 0);
- }
-
- private TreeEntry findMember(final byte[] s, final byte slast, final int offset)
- throws IOException {
- int slash;
- int p;
-
- for (slash = offset; slash < s.length && s[slash] != '/'; slash++) {
- // search for path component terminator
- }
-
- ensureLoaded();
- byte xlast = slash<s.length ? (byte)'/' : slast;
- p = binarySearch(contents, s, xlast, offset, slash);
- if (p >= 0) {
- final TreeEntry r = contents[p];
- if (slash < s.length-1)
- return r instanceof Tree ? ((Tree) r).findMember(s, slast, slash + 1)
- : null;
- return r;
- }
- return null;
- }
-
- /**
- * @param s
- * blob name
- * @return a {@link TreeEntry} representing an object with the specified
- * relative path.
- * @throws IOException
- */
- public TreeEntry findBlobMember(String s) throws IOException {
- return findMember(s,(byte)0);
- }
-
- /**
- * @param s Tree Name
- * @return a Tree with the name s or null
- * @throws IOException
- */
- public TreeEntry findTreeMember(String s) throws IOException {
- return findMember(s,(byte)'/');
- }
-
- private void ensureLoaded() throws IOException, MissingObjectException {
- if (!isLoaded()) {
- ObjectLoader ldr = db.open(getId(), Constants.OBJ_TREE);
- readTree(ldr.getCachedBytes());
- }
- }
-
- private void readTree(final byte[] raw) throws IOException {
- final int rawSize = raw.length;
- int rawPtr = 0;
- TreeEntry[] temp;
- int nextIndex = 0;
-
- while (rawPtr < rawSize) {
- while (rawPtr < rawSize && raw[rawPtr] != 0)
- rawPtr++;
- rawPtr++;
- rawPtr += Constants.OBJECT_ID_LENGTH;
- nextIndex++;
- }
-
- temp = new TreeEntry[nextIndex];
- rawPtr = 0;
- nextIndex = 0;
- while (rawPtr < rawSize) {
- int c = raw[rawPtr++];
- if (c < '0' || c > '7')
- throw new CorruptObjectException(getId(), JGitText.get().corruptObjectInvalidEntryMode);
- int mode = c - '0';
- for (;;) {
- c = raw[rawPtr++];
- if (' ' == c)
- break;
- else if (c < '0' || c > '7')
- throw new CorruptObjectException(getId(), JGitText.get().corruptObjectInvalidMode);
- mode <<= 3;
- mode += c - '0';
- }
-
- int nameLen = 0;
- while (raw[rawPtr + nameLen] != 0)
- nameLen++;
- final byte[] name = new byte[nameLen];
- System.arraycopy(raw, rawPtr, name, 0, nameLen);
- rawPtr += nameLen + 1;
-
- final ObjectId id = ObjectId.fromRaw(raw, rawPtr);
- rawPtr += Constants.OBJECT_ID_LENGTH;
-
- final TreeEntry ent;
- if (FileMode.REGULAR_FILE.equals(mode))
- ent = new FileTreeEntry(this, id, name, false);
- else if (FileMode.EXECUTABLE_FILE.equals(mode))
- ent = new FileTreeEntry(this, id, name, true);
- else if (FileMode.TREE.equals(mode))
- ent = new Tree(this, id, name);
- else if (FileMode.SYMLINK.equals(mode))
- ent = new SymlinkTreeEntry(this, id, name);
- else if (FileMode.GITLINK.equals(mode))
- ent = new GitlinkTreeEntry(this, id, name);
- else
- throw new CorruptObjectException(getId(), MessageFormat.format(
- JGitText.get().corruptObjectInvalidMode2, Integer.toOctalString(mode)));
- temp[nextIndex++] = ent;
- }
-
- contents = temp;
- }
-
- /**
- * Format this Tree in canonical format.
- *
- * @return canonical encoding of the tree object.
- * @throws IOException
- * the tree cannot be loaded, or its not in a writable state.
- */
- public byte[] format() throws IOException {
- TreeFormatter fmt = new TreeFormatter();
- for (TreeEntry e : members()) {
- ObjectId id = e.getId();
- if (id == null)
- throw new ObjectWritingException(MessageFormat.format(JGitText
- .get().objectAtPathDoesNotHaveId, e.getFullName()));
-
- fmt.append(e.getNameUTF8(), e.getMode(), id);
- }
- return fmt.toByteArray();
- }
-
- public String toString() {
- final StringBuilder r = new StringBuilder();
- r.append(ObjectId.toString(getId()));
- r.append(" T "); //$NON-NLS-1$
- r.append(getFullName());
- return r.toString();
- }
-
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java
deleted file mode 100644
index a1ffa68056..0000000000
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TreeEntry.java
+++ /dev/null
@@ -1,256 +0,0 @@
-/*
- * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package org.eclipse.jgit.lib;
-
-import java.io.IOException;
-
-import org.eclipse.jgit.util.RawParseUtils;
-
-/**
- * This class represents an entry in a tree, like a blob or another tree.
- *
- * @deprecated To look up information about a single path, use
- * {@link org.eclipse.jgit.treewalk.TreeWalk#forPath(Repository, String, org.eclipse.jgit.revwalk.RevTree)}.
- * To lookup information about multiple paths at once, use a
- * {@link org.eclipse.jgit.treewalk.TreeWalk} and obtain the current entry's
- * information from its getter methods.
- */
-@Deprecated
-public abstract class TreeEntry implements Comparable {
- private byte[] nameUTF8;
-
- private Tree parent;
-
- private ObjectId id;
-
- /**
- * Construct a named tree entry.
- *
- * @param myParent
- * @param myId
- * @param myNameUTF8
- */
- protected TreeEntry(final Tree myParent, final ObjectId myId,
- final byte[] myNameUTF8) {
- nameUTF8 = myNameUTF8;
- parent = myParent;
- id = myId;
- }
-
- /**
- * @return parent of this tree.
- */
- public Tree getParent() {
- return parent;
- }
-
- /**
- * Delete this entry.
- */
- public void delete() {
- getParent().removeEntry(this);
- detachParent();
- }
-
- /**
- * Detach this entry from it's parent.
- */
- public void detachParent() {
- parent = null;
- }
-
- void attachParent(final Tree p) {
- parent = p;
- }
-
- /**
- * @return the repository owning this entry.
- */
- public Repository getRepository() {
- return getParent().getRepository();
- }
-
- /**
- * @return the raw byte name of this entry.
- */
- public byte[] getNameUTF8() {
- return nameUTF8;
- }
-
- /**
- * @return the name of this entry.
- */
- public String getName() {
- if (nameUTF8 != null)
- return RawParseUtils.decode(nameUTF8);
- return null;
- }
-
- /**
- * Rename this entry.
- *
- * @param n The new name
- * @throws IOException
- */
- public void rename(final String n) throws IOException {
- rename(Constants.encode(n));
- }
-
- /**
- * Rename this entry.
- *
- * @param n The new name
- * @throws IOException
- */
- public void rename(final byte[] n) throws IOException {
- final Tree t = getParent();
- if (t != null) {
- delete();
- }
- nameUTF8 = n;
- if (t != null) {
- t.addEntry(this);
- }
- }
-
- /**
- * @return true if this entry is new or modified since being loaded.
- */
- public boolean isModified() {
- return getId() == null;
- }
-
- /**
- * Mark this entry as modified.
- */
- public void setModified() {
- setId(null);
- }
-
- /**
- * @return SHA-1 of this tree entry (null for new unhashed entries)
- */
- public ObjectId getId() {
- return id;
- }
-
- /**
- * Set (update) the SHA-1 of this entry. Invalidates the id's of all
- * entries above this entry as they will have to be recomputed.
- *
- * @param n SHA-1 for this entry.
- */
- public void setId(final ObjectId n) {
- // If we have a parent and our id is being cleared or changed then force
- // the parent's id to become unset as it depends on our id.
- //
- final Tree p = getParent();
- if (p != null && id != n) {
- if ((id == null && n != null) || (id != null && n == null)
- || !id.equals(n)) {
- p.setId(null);
- }
- }
-
- id = n;
- }
-
- /**
- * @return repository relative name of this entry
- */
- public String getFullName() {
- final StringBuilder r = new StringBuilder();
- appendFullName(r);
- return r.toString();
- }
-
- /**
- * @return repository relative name of the entry
- * FIXME better encoding
- */
- public byte[] getFullNameUTF8() {
- return getFullName().getBytes();
- }
-
- public int compareTo(final Object o) {
- if (this == o)
- return 0;
- if (o instanceof TreeEntry)
- return Tree.compareNames(nameUTF8, ((TreeEntry) o).nameUTF8, lastChar(this), lastChar((TreeEntry)o));
- return -1;
- }
-
- /**
- * Helper for accessing tree/blob methods.
- *
- * @param treeEntry
- * @return '/' for Tree entries and NUL for non-treeish objects.
- */
- final public static int lastChar(TreeEntry treeEntry) {
- if (!(treeEntry instanceof Tree))
- return '\0';
- else
- return '/';
- }
-
- /**
- * @return mode (type of object)
- */
- public abstract FileMode getMode();
-
- private void appendFullName(final StringBuilder r) {
- final TreeEntry p = getParent();
- final String n = getName();
- if (p != null) {
- p.appendFullName(r);
- if (r.length() > 0) {
- r.append('/');
- }
- }
- if (n != null) {
- r.append(n);
- }
- }
-}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
index 191f3d8366..82cbf368c7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeMessageFormatter.java
@@ -46,6 +46,7 @@ import java.util.ArrayList;
import java.util.List;
import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.ChangeIdUtil;
@@ -76,22 +77,22 @@ public class MergeMessageFormatter {
List<String> commits = new ArrayList<String>();
List<String> others = new ArrayList<String>();
for (Ref ref : refsToMerge) {
- if (ref.getName().startsWith(Constants.R_HEADS))
+ if (ref.getName().startsWith(Constants.R_HEADS)) {
branches.add("'" + Repository.shortenRefName(ref.getName()) //$NON-NLS-1$
+ "'"); //$NON-NLS-1$
-
- else if (ref.getName().startsWith(Constants.R_REMOTES))
+ } else if (ref.getName().startsWith(Constants.R_REMOTES)) {
remoteBranches.add("'" //$NON-NLS-1$
+ Repository.shortenRefName(ref.getName()) + "'"); //$NON-NLS-1$
-
- else if (ref.getName().startsWith(Constants.R_TAGS))
+ } else if (ref.getName().startsWith(Constants.R_TAGS)) {
tags.add("'" + Repository.shortenRefName(ref.getName()) + "'"); //$NON-NLS-1$ //$NON-NLS-2$
-
- else if (ref.getName().equals(ref.getObjectId().getName()))
- commits.add("'" + ref.getName() + "'"); //$NON-NLS-1$ //$NON-NLS-2$
-
- else
- others.add(ref.getName());
+ } else {
+ ObjectId objectId = ref.getObjectId();
+ if (objectId != null && ref.getName().equals(objectId.getName())) {
+ commits.add("'" + ref.getName() + "'"); //$NON-NLS-1$ //$NON-NLS-2$
+ } else {
+ others.add(ref.getName());
+ }
+ }
}
List<String> listings = new ArrayList<String>();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java
index 6a2d44bca9..362328a963 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NonNoteEntry.java
@@ -47,6 +47,7 @@ import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.TreeFormatter;
+import org.eclipse.jgit.util.Paths;
/** A tree entry found in a note branch that isn't a valid note. */
class NonNoteEntry extends ObjectId {
@@ -74,27 +75,8 @@ class NonNoteEntry extends ObjectId {
}
int pathCompare(byte[] bBuf, int bPos, int bLen, FileMode bMode) {
- return pathCompare(name, 0, name.length, mode, //
- bBuf, bPos, bLen, bMode);
- }
-
- private static int pathCompare(final byte[] aBuf, int aPos, final int aEnd,
- final FileMode aMode, final byte[] bBuf, int bPos, final int bEnd,
- final FileMode bMode) {
- while (aPos < aEnd && bPos < bEnd) {
- int cmp = (aBuf[aPos++] & 0xff) - (bBuf[bPos++] & 0xff);
- if (cmp != 0)
- return cmp;
- }
-
- if (aPos < aEnd)
- return (aBuf[aPos] & 0xff) - lastPathChar(bMode);
- if (bPos < bEnd)
- return lastPathChar(aMode) - (bBuf[bPos] & 0xff);
- return 0;
- }
-
- private static int lastPathChar(final FileMode mode) {
- return FileMode.TREE.equals(mode.getBits()) ? '/' : '\0';
+ return Paths.compare(
+ name, 0, name.length, mode.getBits(),
+ bBuf, bPos, bLen, bMode.getBits());
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
index c23e4e3288..e67ada6022 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevCommit.java
@@ -44,12 +44,17 @@
package org.eclipse.jgit.revwalk;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import java.io.IOException;
import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.AnyObjectId;
@@ -441,12 +446,12 @@ public class RevCommit extends RevObject {
* @return decoded commit message as a string. Never null.
*/
public final String getFullMessage() {
- final byte[] raw = buffer;
- final int msgB = RawParseUtils.commitMessage(raw, 0);
- if (msgB < 0)
+ byte[] raw = buffer;
+ int msgB = RawParseUtils.commitMessage(raw, 0);
+ if (msgB < 0) {
return ""; //$NON-NLS-1$
- final Charset enc = RawParseUtils.parseEncoding(raw);
- return RawParseUtils.decode(enc, raw, msgB, raw.length);
+ }
+ return RawParseUtils.decode(guessEncoding(), raw, msgB, raw.length);
}
/**
@@ -465,16 +470,17 @@ public class RevCommit extends RevObject {
* spanned multiple lines. Embedded LFs are converted to spaces.
*/
public final String getShortMessage() {
- final byte[] raw = buffer;
- final int msgB = RawParseUtils.commitMessage(raw, 0);
- if (msgB < 0)
+ byte[] raw = buffer;
+ int msgB = RawParseUtils.commitMessage(raw, 0);
+ if (msgB < 0) {
return ""; //$NON-NLS-1$
+ }
- final Charset enc = RawParseUtils.parseEncoding(raw);
- final int msgE = RawParseUtils.endOfParagraph(raw, msgB);
- String str = RawParseUtils.decode(enc, raw, msgB, msgE);
- if (hasLF(raw, msgB, msgE))
+ int msgE = RawParseUtils.endOfParagraph(raw, msgB);
+ String str = RawParseUtils.decode(guessEncoding(), raw, msgB, msgE);
+ if (hasLF(raw, msgB, msgE)) {
str = StringUtils.replaceLineBreaksWithSpace(str);
+ }
return str;
}
@@ -488,18 +494,49 @@ public class RevCommit extends RevObject {
/**
* Determine the encoding of the commit message buffer.
* <p>
+ * Locates the "encoding" header (if present) and returns its value. Due to
+ * corruption in the wild this may be an invalid encoding name that is not
+ * recognized by any character encoding library.
+ * <p>
+ * If no encoding header is present, null.
+ *
+ * @return the preferred encoding of {@link #getRawBuffer()}; or null.
+ * @since 4.2
+ */
+ @Nullable
+ public final String getEncodingName() {
+ return RawParseUtils.parseEncodingName(buffer);
+ }
+
+ /**
+ * Determine the encoding of the commit message buffer.
+ * <p>
* Locates the "encoding" header (if present) and then returns the proper
* character set to apply to this buffer to evaluate its contents as
* character data.
* <p>
- * If no encoding header is present, {@link Constants#CHARSET} is assumed.
+ * If no encoding header is present {@code UTF-8} is assumed.
*
* @return the preferred encoding of {@link #getRawBuffer()}.
+ * @throws IllegalCharsetNameException
+ * if the character set requested by the encoding header is
+ * malformed and unsupportable.
+ * @throws UnsupportedCharsetException
+ * if the JRE does not support the character set requested by
+ * the encoding header.
*/
public final Charset getEncoding() {
return RawParseUtils.parseEncoding(buffer);
}
+ private Charset guessEncoding() {
+ try {
+ return getEncoding();
+ } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
+ return UTF_8;
+ }
+ }
+
/**
* Parse the footer lines (e.g. "Signed-off-by") for machine processing.
* <p>
@@ -529,7 +566,7 @@ public class RevCommit extends RevObject {
final int msgB = RawParseUtils.commitMessage(raw, 0);
final ArrayList<FooterLine> r = new ArrayList<FooterLine>(4);
- final Charset enc = getEncoding();
+ final Charset enc = guessEncoding();
for (;;) {
ptr = RawParseUtils.prevLF(raw, ptr);
if (ptr <= msgB)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
index bf2785e0d7..81a54bf7ea 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
@@ -45,8 +45,12 @@
package org.eclipse.jgit.revwalk;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
import java.io.IOException;
import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -162,7 +166,7 @@ public class RevTag extends RevObject {
int p = pos.value += 4; // "tag "
final int nameEnd = RawParseUtils.nextLF(rawTag, p) - 1;
- tagName = RawParseUtils.decode(Constants.CHARSET, rawTag, p, nameEnd);
+ tagName = RawParseUtils.decode(UTF_8, rawTag, p, nameEnd);
if (walk.isRetainBody())
buffer = rawTag;
@@ -207,12 +211,12 @@ public class RevTag extends RevObject {
* @return decoded tag message as a string. Never null.
*/
public final String getFullMessage() {
- final byte[] raw = buffer;
- final int msgB = RawParseUtils.tagMessage(raw, 0);
- if (msgB < 0)
+ byte[] raw = buffer;
+ int msgB = RawParseUtils.tagMessage(raw, 0);
+ if (msgB < 0) {
return ""; //$NON-NLS-1$
- final Charset enc = RawParseUtils.parseEncoding(raw);
- return RawParseUtils.decode(enc, raw, msgB, raw.length);
+ }
+ return RawParseUtils.decode(guessEncoding(), raw, msgB, raw.length);
}
/**
@@ -231,19 +235,28 @@ public class RevTag extends RevObject {
* multiple lines. Embedded LFs are converted to spaces.
*/
public final String getShortMessage() {
- final byte[] raw = buffer;
- final int msgB = RawParseUtils.tagMessage(raw, 0);
- if (msgB < 0)
+ byte[] raw = buffer;
+ int msgB = RawParseUtils.tagMessage(raw, 0);
+ if (msgB < 0) {
return ""; //$NON-NLS-1$
+ }
- final Charset enc = RawParseUtils.parseEncoding(raw);
- final int msgE = RawParseUtils.endOfParagraph(raw, msgB);
- String str = RawParseUtils.decode(enc, raw, msgB, msgE);
- if (RevCommit.hasLF(raw, msgB, msgE))
+ int msgE = RawParseUtils.endOfParagraph(raw, msgB);
+ String str = RawParseUtils.decode(guessEncoding(), raw, msgB, msgE);
+ if (RevCommit.hasLF(raw, msgB, msgE)) {
str = StringUtils.replaceLineBreaksWithSpace(str);
+ }
return str;
}
+ private Charset guessEncoding() {
+ try {
+ return RawParseUtils.parseEncoding(buffer);
+ } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
+ return UTF_8;
+ }
+ }
+
/**
* Get a reference to the object this tag was placed on.
* <p>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
index 7f9cec734d..aa36aeb1be 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
@@ -143,7 +143,9 @@ abstract class BasePackConnection extends BaseConnection {
final int timeout = transport.getTimeout();
if (timeout > 0) {
final Thread caller = Thread.currentThread();
- myTimer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$
+ if (myTimer == null) {
+ myTimer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$
+ }
timeoutIn = new TimeoutInputStream(myIn, myTimer);
timeoutOut = new TimeoutOutputStream(myOut, myTimer);
timeoutIn.setTimeout(timeout * 1000);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
index cf13582db5..754cf361a9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
@@ -464,8 +464,12 @@ public abstract class BasePackFetchConnection extends BasePackConnection
final PacketLineOut p = statelessRPC ? pckState : pckOut;
boolean first = true;
for (final Ref r : want) {
+ ObjectId objectId = r.getObjectId();
+ if (objectId == null) {
+ continue;
+ }
try {
- if (walk.parseAny(r.getObjectId()).has(REACHABLE)) {
+ if (walk.parseAny(objectId).has(REACHABLE)) {
// We already have this object. Asking for it is
// not a very good idea.
//
@@ -478,7 +482,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection
final StringBuilder line = new StringBuilder(46);
line.append("want "); //$NON-NLS-1$
- line.append(r.getObjectId().name());
+ line.append(objectId.name());
if (first) {
line.append(enableCapabilities());
first = false;
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 0834c359aa..963de35d41 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
@@ -239,8 +239,11 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
final StringBuilder sb = new StringBuilder();
ObjectId oldId = rru.getExpectedOldObjectId();
if (oldId == null) {
- Ref adv = getRef(rru.getRemoteName());
- oldId = adv != null ? adv.getObjectId() : ObjectId.zeroId();
+ final Ref advertised = getRef(rru.getRemoteName());
+ oldId = advertised != null ? advertised.getObjectId() : null;
+ if (oldId == null) {
+ oldId = ObjectId.zeroId();
+ }
}
sb.append(oldId.name());
sb.append(' ');
@@ -382,7 +385,8 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
final int oldTimeout = timeoutIn.getTimeout();
final int sendTime = (int) Math.min(packTransferTime, 28800000L);
try {
- timeoutIn.setTimeout(10 * Math.max(sendTime, oldTimeout));
+ int timeout = 10 * Math.max(sendTime, oldTimeout);
+ timeoutIn.setTimeout((timeout < 0) ? Integer.MAX_VALUE : timeout);
return pckIn.readString();
} finally {
timeoutIn.setTimeout(oldTimeout);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
index 776a9f695a..a20e652553 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
@@ -293,18 +293,20 @@ public abstract class BaseReceivePack {
db = into;
walk = new RevWalk(db);
- final ReceiveConfig cfg = db.getConfig().get(ReceiveConfig.KEY);
- objectChecker = cfg.newObjectChecker();
- allowCreates = cfg.allowCreates;
+ TransferConfig tc = db.getConfig().get(TransferConfig.KEY);
+ objectChecker = tc.newReceiveObjectChecker();
+
+ ReceiveConfig rc = db.getConfig().get(ReceiveConfig.KEY);
+ allowCreates = rc.allowCreates;
allowAnyDeletes = true;
- allowBranchDeletes = cfg.allowDeletes;
- allowNonFastForwards = cfg.allowNonFastForwards;
- allowOfsDelta = cfg.allowOfsDelta;
+ allowBranchDeletes = rc.allowDeletes;
+ allowNonFastForwards = rc.allowNonFastForwards;
+ allowOfsDelta = rc.allowOfsDelta;
advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
refFilter = RefFilter.DEFAULT;
advertisedHaves = new HashSet<ObjectId>();
clientShallowCommits = new HashSet<ObjectId>();
- signedPushConfig = cfg.signedPush;
+ signedPushConfig = rc.signedPush;
}
/** Configuration for receive operations. */
@@ -315,32 +317,13 @@ public abstract class BaseReceivePack {
}
};
- final boolean checkReceivedObjects;
- final boolean allowLeadingZeroFileMode;
- final boolean allowInvalidPersonIdent;
- final boolean safeForWindows;
- final boolean safeForMacOS;
-
final boolean allowCreates;
final boolean allowDeletes;
final boolean allowNonFastForwards;
final boolean allowOfsDelta;
-
final SignedPushConfig signedPush;
ReceiveConfig(final Config config) {
- checkReceivedObjects = config.getBoolean(
- "receive", "fsckobjects", //$NON-NLS-1$ //$NON-NLS-2$
- config.getBoolean("transfer", "fsckobjects", false)); //$NON-NLS-1$ //$NON-NLS-2$
- allowLeadingZeroFileMode = checkReceivedObjects
- && config.getBoolean("fsck", "allowLeadingZeroFileMode", false); //$NON-NLS-1$ //$NON-NLS-2$
- allowInvalidPersonIdent = checkReceivedObjects
- && config.getBoolean("fsck", "allowInvalidPersonIdent", false); //$NON-NLS-1$ //$NON-NLS-2$
- safeForWindows = checkReceivedObjects
- && config.getBoolean("fsck", "safeForWindows", false); //$NON-NLS-1$ //$NON-NLS-2$
- safeForMacOS = checkReceivedObjects
- && config.getBoolean("fsck", "safeForMacOS", false); //$NON-NLS-1$ //$NON-NLS-2$
-
allowCreates = true;
allowDeletes = !config.getBoolean("receive", "denydeletes", false); //$NON-NLS-1$ //$NON-NLS-2$
allowNonFastForwards = !config.getBoolean("receive", //$NON-NLS-1$
@@ -349,16 +332,6 @@ public abstract class BaseReceivePack {
true);
signedPush = SignedPushConfig.KEY.parse(config);
}
-
- ObjectChecker newObjectChecker() {
- if (!checkReceivedObjects)
- return null;
- return new ObjectChecker()
- .setAllowLeadingZeroFileMode(allowLeadingZeroFileMode)
- .setAllowInvalidPersonIdent(allowInvalidPersonIdent)
- .setSafeForWindows(safeForWindows)
- .setSafeForMacOS(safeForMacOS);
- }
}
/**
@@ -1372,16 +1345,21 @@ public abstract class BaseReceivePack {
}
}
- if (cmd.getType() == ReceiveCommand.Type.DELETE && ref != null
- && !ObjectId.zeroId().equals(cmd.getOldId())
- && !ref.getObjectId().equals(cmd.getOldId())) {
- // Delete commands can be sent with the old id matching our
- // advertised value, *OR* with the old id being 0{40}. Any
- // other requested old id is invalid.
- //
- cmd.setResult(Result.REJECTED_OTHER_REASON,
- JGitText.get().invalidOldIdSent);
- 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) {
@@ -1391,8 +1369,15 @@ public abstract class BaseReceivePack {
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 (!ref.getObjectId().equals(cmd.getOldId())) {
+ if (!id.equals(cmd.getOldId())) {
// A properly functioning client will send the same
// object id we advertised.
//
@@ -1468,10 +1453,7 @@ public abstract class BaseReceivePack {
* @since 3.6
*/
protected void failPendingCommands() {
- for (ReceiveCommand cmd : commands) {
- if (cmd.getResult() == Result.NOT_ATTEMPTED)
- cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().transactionAborted);
- }
+ ReceiveCommand.abort(commands);
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java
index 3e0ee2f645..3941d3c552 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ChainingCredentialsProvider.java
@@ -113,19 +113,18 @@ public class ChainingCredentialsProvider extends CredentialsProvider {
throws UnsupportedCredentialItem {
for (CredentialsProvider p : credentialProviders) {
if (p.supports(items)) {
- p.get(uri, items);
- if (isAnyNull(items))
+ if (!p.get(uri, items)) {
+ if (p.isInteractive()) {
+ return false; // user cancelled the request
+ }
continue;
+ }
+ if (isAnyNull(items)) {
+ continue;
+ }
return true;
}
}
return false;
}
-
- private boolean isAnyNull(CredentialItem... items) {
- for (CredentialItem i : items)
- if (i == null)
- return true;
- return false;
- }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
index 0ff9fcea74..da288ec31e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
@@ -59,8 +59,7 @@ import org.eclipse.jgit.lib.Ref;
*
* @see Transport
*/
-public interface Connection {
-
+public interface Connection extends AutoCloseable {
/**
* Get the complete map of refs advertised as available for fetching or
* pushing.
@@ -108,6 +107,10 @@ public interface Connection {
* <p>
* If additional messages were produced by the remote peer, these should
* still be retained in the connection instance for {@link #getMessages()}.
+ * <p>
+ * {@code AutoClosable.close()} declares that it throws {@link Exception}.
+ * Implementers shouldn't throw checked exceptions. This override narrows
+ * the signature to prevent them from doing so.
*/
public void close();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java
index 464d0f9ee5..4800f6826f 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/CredentialsProvider.java
@@ -81,6 +81,20 @@ public abstract class CredentialsProvider {
}
/**
+ * @param items
+ * credential items to check
+ * @return {@code true} if any of the passed items is null, {@code false}
+ * otherwise
+ * @since 4.2
+ */
+ protected static boolean isAnyNull(CredentialItem... items) {
+ for (CredentialItem i : items)
+ if (i == null)
+ return true;
+ return false;
+ }
+
+ /**
* Check if the provider is interactive with the end-user.
*
* An interactive provider may try to open a dialog box, or prompt for input
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
index 9aae1c37aa..c4b3f83048 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
@@ -397,11 +397,17 @@ class FetchProcess {
private void expandFetchTags() throws TransportException {
final Map<String, Ref> haveRefs = localRefs();
for (final Ref r : conn.getRefs()) {
- if (!isTag(r))
+ if (!isTag(r)) {
+ continue;
+ }
+ ObjectId id = r.getObjectId();
+ if (id == null) {
continue;
+ }
final Ref local = haveRefs.get(r.getName());
- if (local == null || !r.getObjectId().equals(local.getObjectId()))
+ if (local == null || !id.equals(local.getObjectId())) {
wantTag(r);
+ }
}
}
@@ -413,6 +419,11 @@ class FetchProcess {
private void want(final Ref src, final RefSpec spec)
throws TransportException {
final ObjectId newId = src.getObjectId();
+ if (newId == null) {
+ throw new NullPointerException(MessageFormat.format(
+ JGitText.get().transportProvidedRefWithNoObjectId,
+ src.getName()));
+ }
if (spec.getDestination() != null) {
final TrackingRefUpdate tru = createUpdate(spec, newId);
if (newId.equals(tru.getOldObjectId()))
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
index 85109a5bf0..1dfe5d9797 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java
@@ -149,14 +149,27 @@ public class JschSession implements RemoteSession {
channel.setCommand(commandName);
setupStreams();
channel.connect(timeout > 0 ? timeout * 1000 : 0);
- if (!channel.isConnected())
+ if (!channel.isConnected()) {
+ closeOutputStream();
throw new TransportException(uri,
JGitText.get().connectionFailed);
+ }
} catch (JSchException e) {
+ closeOutputStream();
throw new TransportException(uri, e.getMessage(), e);
}
}
+ private void closeOutputStream() {
+ if (outputStream != null) {
+ try {
+ outputStream.close();
+ } catch (IOException ioe) {
+ // ignore
+ }
+ }
+ }
+
private void setupStreams() throws IOException {
inputStream = channel.getInputStream();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java
index 74909998ce..4037545e9d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/NetRCCredentialsProvider.java
@@ -105,12 +105,11 @@ public class NetRCCredentialsProvider extends CredentialsProvider {
throw new UnsupportedCredentialItem(uri, i.getClass().getName()
+ ":" + i.getPromptText()); //$NON-NLS-1$
}
- return true;
+ return !isAnyNull(items);
}
@Override
public boolean isInteractive() {
return false;
}
-
}
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 6e5fc9f009..b96fe885e1 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java
@@ -1049,8 +1049,11 @@ public abstract class PackParser {
final byte[] data) throws IOException {
if (objCheck != null) {
try {
- objCheck.check(type, data);
+ objCheck.check(id, type, data);
} catch (CorruptObjectException e) {
+ if (e.getErrorType() != null) {
+ throw e;
+ }
throw new CorruptObjectException(MessageFormat.format(
JGitText.get().invalidObject,
Constants.typeString(type),
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java
new file mode 100644
index 0000000000..ac048a14a9
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProgressSpinner.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2015, 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 java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A simple spinner connected to an {@code OutputStream}.
+ * <p>
+ * This is class is not thread-safe. The update method may only be used from a
+ * single thread. Updates are sent only as frequently as {@link #update()} is
+ * invoked by the caller, and are capped at no more than 2 times per second by
+ * requiring at least 500 milliseconds between updates.
+ *
+ * @since 4.2
+ */
+public class ProgressSpinner {
+ private static final long MIN_REFRESH_MILLIS = 500;
+ private static final char[] STATES = new char[] { '-', '\\', '|', '/' };
+
+ private final OutputStream out;
+ private String msg;
+ private int state;
+ private boolean write;
+ private boolean shown;
+ private long nextUpdateMillis;
+
+ /**
+ * Initialize a new spinner.
+ *
+ * @param out
+ * where to send output to.
+ */
+ public ProgressSpinner(OutputStream out) {
+ this.out = out;
+ this.write = true;
+ }
+
+ /**
+ * Begin a time consuming task.
+ *
+ * @param title
+ * description of the task, suitable for human viewing.
+ * @param delay
+ * delay to wait before displaying anything at all.
+ * @param delayUnits
+ * unit for {@code delay}.
+ */
+ public void beginTask(String title, long delay, TimeUnit delayUnits) {
+ msg = title;
+ state = 0;
+ shown = false;
+
+ long now = System.currentTimeMillis();
+ if (delay > 0) {
+ nextUpdateMillis = now + delayUnits.toMillis(delay);
+ } else {
+ send(now);
+ }
+ }
+
+ /** Update the spinner if it is showing. */
+ public void update() {
+ long now = System.currentTimeMillis();
+ if (now >= nextUpdateMillis) {
+ send(now);
+ state = (state + 1) % STATES.length;
+ }
+ }
+
+ private void send(long now) {
+ StringBuilder buf = new StringBuilder(msg.length() + 16);
+ buf.append('\r').append(msg).append("... ("); //$NON-NLS-1$
+ buf.append(STATES[state]);
+ buf.append(") "); //$NON-NLS-1$
+ shown = true;
+ write(buf.toString());
+ nextUpdateMillis = now + MIN_REFRESH_MILLIS;
+ }
+
+ /**
+ * Denote the current task completed.
+ *
+ * @param result
+ * text to print after the task's title
+ * {@code "$title ... $result"}.
+ */
+ public void endTask(String result) {
+ if (shown) {
+ write('\r' + msg + "... " + result + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ private void write(String s) {
+ if (write) {
+ try {
+ out.write(s.getBytes(UTF_8));
+ out.flush();
+ } catch (IOException e) {
+ write = false;
+ }
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
index 4fd192dbb2..5cea88215a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
@@ -188,8 +188,13 @@ class PushProcess {
final Map<String, RemoteRefUpdate> result = new HashMap<String, RemoteRefUpdate>();
for (final RemoteRefUpdate rru : toPush.values()) {
final Ref advertisedRef = connection.getRef(rru.getRemoteName());
- final ObjectId advertisedOld = (advertisedRef == null ? ObjectId
- .zeroId() : advertisedRef.getObjectId());
+ ObjectId advertisedOld = null;
+ if (advertisedRef != null) {
+ advertisedOld = advertisedRef.getObjectId();
+ }
+ if (advertisedOld == null) {
+ advertisedOld = ObjectId.zeroId();
+ }
if (rru.getNewObjectId().equals(advertisedOld)) {
if (rru.isDelete()) {
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 5702b6d7b9..2b21c4a8fe 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
@@ -43,6 +43,9 @@
package org.eclipse.jgit.transport;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
+import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
+
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
@@ -168,6 +171,25 @@ public class ReceiveCommand {
return filter((Iterable<ReceiveCommand>) commands, want);
}
+ /**
+ * Set unprocessed commands as failed due to transaction aborted.
+ * <p>
+ * If a command is still {@link Result#NOT_ATTEMPTED} it will be set to
+ * {@link Result#REJECTED_OTHER_REASON}.
+ *
+ * @param commands
+ * commands to mark as failed.
+ * @since 4.2
+ */
+ public static void abort(Iterable<ReceiveCommand> commands) {
+ for (ReceiveCommand c : commands) {
+ if (c.getResult() == NOT_ATTEMPTED) {
+ c.setResult(REJECTED_OTHER_REASON,
+ JGitText.get().transactionAborted);
+ }
+ }
+ }
+
private final ObjectId oldId;
private final ObjectId newId;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
index 66ffc3abe9..0e803bdaf7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefSpec.java
@@ -457,10 +457,6 @@ public class RefSpec implements Serializable {
if (i != -1) {
if (s.indexOf('*', i + 1) > i)
return false;
- if (i > 0 && s.charAt(i - 1) != '/')
- return false;
- if (i < s.length() - 1 && s.charAt(i + 1) != '/')
- return false;
}
return true;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
index cf388e2718..fe9f2a3155 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandInputStream.java
@@ -78,12 +78,8 @@ import org.eclipse.jgit.util.RawParseUtils;
* @see SideBandOutputStream
*/
class SideBandInputStream extends InputStream {
- private static final String PFX_REMOTE = JGitText.get().prefixRemote;
-
static final int CH_DATA = 1;
-
static final int CH_PROGRESS = 2;
-
static final int CH_ERROR = 3;
private static Pattern P_UNBOUNDED = Pattern
@@ -174,7 +170,7 @@ class SideBandInputStream extends InputStream {
continue;
case CH_ERROR:
eof = true;
- throw new TransportException(PFX_REMOTE + readString(available));
+ throw new TransportException(remote(readString(available)));
default:
throw new PackProtocolException(
MessageFormat.format(JGitText.get().invalidChannel,
@@ -241,7 +237,18 @@ class SideBandInputStream extends InputStream {
}
private void beginTask(final int totalWorkUnits) {
- monitor.beginTask(PFX_REMOTE + currentTask, totalWorkUnits);
+ monitor.beginTask(remote(currentTask), totalWorkUnits);
+ }
+
+ private static String remote(String msg) {
+ String prefix = JGitText.get().prefixRemote;
+ StringBuilder r = new StringBuilder(prefix.length() + msg.length() + 1);
+ r.append(prefix);
+ if (prefix.length() > 0 && prefix.charAt(prefix.length() - 1) != ' ') {
+ r.append(' ');
+ }
+ r.append(msg);
+ return r.toString();
}
private String readString(final int len) throws IOException {
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 f0c513427a..72c9c8b93e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
@@ -43,12 +43,20 @@
package org.eclipse.jgit.transport;
+import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase;
+import static org.eclipse.jgit.util.StringUtils.toLowerCase;
+
+import java.io.File;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.storage.file.LazyObjectIdSetFile;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Config.SectionParser;
import org.eclipse.jgit.lib.ObjectChecker;
+import org.eclipse.jgit.lib.ObjectIdSet;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.SystemReader;
@@ -58,6 +66,8 @@ import org.eclipse.jgit.util.SystemReader;
* parameters.
*/
public class TransferConfig {
+ private static final String FSCK = "fsck"; //$NON-NLS-1$
+
/** Key for {@link Config#get(SectionParser)}. */
public static final Config.SectionParser<TransferConfig> KEY = new SectionParser<TransferConfig>() {
public TransferConfig parse(final Config cfg) {
@@ -65,8 +75,14 @@ public class TransferConfig {
}
};
- private final boolean checkReceivedObjects;
- private final boolean allowLeadingZeroFileMode;
+ enum FsckMode {
+ ERROR, WARN, IGNORE;
+ }
+
+ private final boolean fetchFsck;
+ private final boolean receiveFsck;
+ private final String fsckSkipList;
+ private final EnumSet<ObjectChecker.ErrorType> ignore;
private final boolean allowInvalidPersonIdent;
private final boolean safeForWindows;
private final boolean safeForMacOS;
@@ -79,20 +95,47 @@ public class TransferConfig {
}
TransferConfig(final Config rc) {
- checkReceivedObjects = rc.getBoolean(
- "fetch", "fsckobjects", //$NON-NLS-1$ //$NON-NLS-2$
- rc.getBoolean("transfer", "fsckobjects", false)); //$NON-NLS-1$ //$NON-NLS-2$
- allowLeadingZeroFileMode = checkReceivedObjects
- && rc.getBoolean("fsck", "allowLeadingZeroFileMode", false); //$NON-NLS-1$ //$NON-NLS-2$
- allowInvalidPersonIdent = checkReceivedObjects
- && rc.getBoolean("fsck", "allowInvalidPersonIdent", false); //$NON-NLS-1$ //$NON-NLS-2$
- safeForWindows = checkReceivedObjects
- && rc.getBoolean("fsck", "safeForWindows", //$NON-NLS-1$ //$NON-NLS-2$
+ boolean fsck = rc.getBoolean("transfer", "fsckobjects", false); //$NON-NLS-1$ //$NON-NLS-2$
+ fetchFsck = rc.getBoolean("fetch", "fsckobjects", fsck); //$NON-NLS-1$ //$NON-NLS-2$
+ receiveFsck = rc.getBoolean("receive", "fsckobjects", fsck); //$NON-NLS-1$ //$NON-NLS-2$
+ fsckSkipList = rc.getString(FSCK, null, "skipList"); //$NON-NLS-1$
+ allowInvalidPersonIdent = rc.getBoolean(FSCK, "allowInvalidPersonIdent", false); //$NON-NLS-1$
+ safeForWindows = rc.getBoolean(FSCK, "safeForWindows", //$NON-NLS-1$
SystemReader.getInstance().isWindows());
- safeForMacOS = checkReceivedObjects
- && rc.getBoolean("fsck", "safeForMacOS", //$NON-NLS-1$ //$NON-NLS-2$
+ safeForMacOS = rc.getBoolean(FSCK, "safeForMacOS", //$NON-NLS-1$
SystemReader.getInstance().isMacOS());
+ ignore = EnumSet.noneOf(ObjectChecker.ErrorType.class);
+ EnumSet<ObjectChecker.ErrorType> set = EnumSet
+ .noneOf(ObjectChecker.ErrorType.class);
+ for (String key : rc.getNames(FSCK)) {
+ if (equalsIgnoreCase(key, "skipList") //$NON-NLS-1$
+ || equalsIgnoreCase(key, "allowLeadingZeroFileMode") //$NON-NLS-1$
+ || equalsIgnoreCase(key, "allowInvalidPersonIdent") //$NON-NLS-1$
+ || equalsIgnoreCase(key, "safeForWindows") //$NON-NLS-1$
+ || equalsIgnoreCase(key, "safeForMacOS")) { //$NON-NLS-1$
+ continue;
+ }
+
+ ObjectChecker.ErrorType id = FsckKeyNameHolder.parse(key);
+ if (id != null) {
+ switch (rc.getEnum(FSCK, null, key, FsckMode.ERROR)) {
+ case ERROR:
+ ignore.remove(id);
+ break;
+ case WARN:
+ case IGNORE:
+ ignore.add(id);
+ break;
+ }
+ set.add(id);
+ }
+ }
+ if (!set.contains(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE)
+ && rc.getBoolean(FSCK, "allowLeadingZeroFileMode", false)) { //$NON-NLS-1$
+ ignore.add(ObjectChecker.ErrorType.ZERO_PADDED_FILEMODE);
+ }
+
allowTipSha1InWant = rc.getBoolean(
"uploadpack", "allowtipsha1inwant", false); //$NON-NLS-1$ //$NON-NLS-2$
allowReachableSha1InWant = rc.getBoolean(
@@ -105,14 +148,38 @@ public class TransferConfig {
* enabled in the repository configuration.
* @since 3.6
*/
+ @Nullable
public ObjectChecker newObjectChecker() {
- if (!checkReceivedObjects)
+ return newObjectChecker(fetchFsck);
+ }
+
+ /**
+ * @return checker to verify objects pushed into this repository, or null if
+ * checking is not enabled in the repository configuration.
+ * @since 4.2
+ */
+ @Nullable
+ public ObjectChecker newReceiveObjectChecker() {
+ return newObjectChecker(receiveFsck);
+ }
+
+ private ObjectChecker newObjectChecker(boolean check) {
+ if (!check) {
return null;
+ }
return new ObjectChecker()
- .setAllowLeadingZeroFileMode(allowLeadingZeroFileMode)
+ .setIgnore(ignore)
.setAllowInvalidPersonIdent(allowInvalidPersonIdent)
.setSafeForWindows(safeForWindows)
- .setSafeForMacOS(safeForMacOS);
+ .setSafeForMacOS(safeForMacOS)
+ .setSkipList(skipList());
+ }
+
+ private ObjectIdSet skipList() {
+ if (fsckSkipList != null && !fsckSkipList.isEmpty()) {
+ return new LazyObjectIdSetFile(new File(fsckSkipList));
+ }
+ return null;
}
/**
@@ -161,4 +228,34 @@ public class TransferConfig {
}
};
}
+
+ static class FsckKeyNameHolder {
+ private static final Map<String, ObjectChecker.ErrorType> errors;
+
+ static {
+ errors = new HashMap<>();
+ for (ObjectChecker.ErrorType m : ObjectChecker.ErrorType.values()) {
+ errors.put(keyNameFor(m.name()), m);
+ }
+ }
+
+ @Nullable
+ static ObjectChecker.ErrorType parse(String key) {
+ return errors.get(toLowerCase(key));
+ }
+
+ private static String keyNameFor(String name) {
+ StringBuilder r = new StringBuilder(name.length());
+ for (int i = 0; i < name.length(); i++) {
+ char c = name.charAt(i);
+ if (c != '_') {
+ r.append(c);
+ }
+ }
+ return toLowerCase(r.toString());
+ }
+
+ private FsckKeyNameHolder() {
+ }
+ }
}
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 6af153cbc9..9e6d1f68f5 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -98,7 +98,7 @@ import org.eclipse.jgit.storage.pack.PackConfig;
* Transport instances and the connections they create are not thread-safe.
* Callers must ensure a transport is accessed by only one thread at a time.
*/
-public abstract class Transport {
+public abstract class Transport implements AutoCloseable {
/** Type of operation a Transport is being opened for. */
public enum Operation {
/** Transport is to fetch objects locally. */
@@ -1353,6 +1353,10 @@ public abstract class Transport {
* must close that network socket, disconnecting the two peers. If the
* remote repository is actually local (same system) this method must close
* any open file handles used to read the "remote" repository.
+ * <p>
+ * {@code AutoClosable.close()} declares that it throws {@link Exception}.
+ * Implementers shouldn't throw checked exceptions. This override narrows
+ * the signature to prevent them from doing so.
*/
public abstract void close();
}
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 2f9dfa1d6b..3ee2feb140 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java
@@ -83,7 +83,7 @@ public class URIish implements Serializable {
* capturing groups: the first containing the user and the second containing
* the password
*/
- private static final String OPT_USER_PWD_P = "(?:([^/:@]+)(?::([^\\\\/]+))?@)?"; //$NON-NLS-1$
+ private static final String OPT_USER_PWD_P = "(?:([^/:]+)(?::([^\\\\/]+))?@)?"; //$NON-NLS-1$
/**
* Part of a pattern which matches the host part of URIs. Defines one
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
index 1c6b8b7363..17edfdc4fb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
@@ -267,6 +267,10 @@ class WalkFetchConnection extends BaseFetchConnection {
final HashSet<ObjectId> inWorkQueue = new HashSet<ObjectId>();
for (final Ref r : want) {
final ObjectId id = r.getObjectId();
+ if (id == null) {
+ throw new NullPointerException(MessageFormat.format(
+ JGitText.get().transportProvidedRefWithNoObjectId, r.getName()));
+ }
try {
final RevObject obj = revWalk.parseAny(id);
if (obj.has(COMPLETE))
@@ -633,10 +637,11 @@ class WalkFetchConnection extends BaseFetchConnection {
final byte[] raw = uol.getCachedBytes();
if (objCheck != null) {
try {
- objCheck.check(type, raw);
+ objCheck.check(id, type, raw);
} catch (CorruptObjectException e) {
- throw new TransportException(MessageFormat.format(JGitText.get().transportExceptionInvalid
- , Constants.typeString(type), id.name(), e.getMessage()));
+ throw new TransportException(MessageFormat.format(
+ JGitText.get().transportExceptionInvalid,
+ Constants.typeString(type), id.name(), e.getMessage()));
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
index 5e71889574..58136355eb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
@@ -59,6 +59,7 @@ import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.eclipse.jgit.util.Paths;
/**
* Walks a Git tree (directory) in Git sort order.
@@ -382,20 +383,9 @@ public abstract class AbstractTreeIterator {
}
private int pathCompare(byte[] b, int bPos, int bEnd, int bMode, int aPos) {
- final byte[] a = path;
- final int aEnd = pathLen;
-
- for (; aPos < aEnd && bPos < bEnd; aPos++, bPos++) {
- final int cmp = (a[aPos] & 0xff) - (b[bPos] & 0xff);
- if (cmp != 0)
- return cmp;
- }
-
- if (aPos < aEnd)
- return (a[aPos] & 0xff) - lastPathChar(bMode);
- if (bPos < bEnd)
- return lastPathChar(mode) - (b[bPos] & 0xff);
- return lastPathChar(mode) - lastPathChar(bMode);
+ return Paths.compare(
+ path, aPos, pathLen, mode,
+ b, bPos, bEnd, bMode);
}
private static int alreadyMatch(AbstractTreeIterator a,
@@ -412,10 +402,6 @@ public abstract class AbstractTreeIterator {
}
}
- private static int lastPathChar(final int mode) {
- return FileMode.TREE.equals(mode) ? '/' : '\0';
- }
-
/**
* Check if the current entry of both iterators has the same id.
* <p>
@@ -692,6 +678,14 @@ public abstract class AbstractTreeIterator {
}
/**
+ * @return true if the iterator implements {@link #stopWalk()}.
+ * @since 4.2
+ */
+ protected boolean needsStopWalk() {
+ return false;
+ }
+
+ /**
* @return the length of the name component of the path for the current entry
*/
public int getNameLength() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java
index 8dbf80e6a8..ec4a84eff3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/EmptyTreeIterator.java
@@ -142,4 +142,9 @@ public class EmptyTreeIterator extends AbstractTreeIterator {
if (parent != null)
parent.stopWalk();
}
+
+ @Override
+ protected boolean needsStopWalk() {
+ return parent != null && parent.needsStopWalk();
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
index 350f563964..d2195a874c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java
@@ -43,6 +43,8 @@
package org.eclipse.jgit.treewalk;
+import java.io.IOException;
+
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.lib.FileMode;
@@ -338,6 +340,41 @@ public class NameConflictTreeWalk extends TreeWalk {
dfConflict = null;
}
+ void stopWalk() throws IOException {
+ if (!needsStopWalk()) {
+ return;
+ }
+
+ // Name conflicts make aborting early difficult. Multiple paths may
+ // exist between the file and directory versions of a name. To ensure
+ // the directory version is skipped over (as it was previously visited
+ // during the file version step) requires popping up the stack and
+ // finishing out each subtree that the walker dove into. Siblings in
+ // parents do not need to be recursed into, bounding the cost.
+ for (;;) {
+ AbstractTreeIterator t = min();
+ if (t.eof()) {
+ if (depth > 0) {
+ exitSubtree();
+ popEntriesEqual();
+ continue;
+ }
+ return;
+ }
+ currentHead = t;
+ skipEntriesEqual();
+ }
+ }
+
+ private boolean needsStopWalk() {
+ for (AbstractTreeIterator t : trees) {
+ if (t.needsStopWalk()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* True if the current entry is covered by a directory/file conflict.
*
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 06dc0bf6d0..5cd713da78 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
@@ -57,6 +57,7 @@ import org.eclipse.jgit.attributes.Attributes;
import org.eclipse.jgit.attributes.AttributesNode;
import org.eclipse.jgit.attributes.AttributesNodeProvider;
import org.eclipse.jgit.attributes.AttributesProvider;
+import org.eclipse.jgit.dircache.DirCacheBuildIterator;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -256,7 +257,7 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
private boolean postOrderTraversal;
- private int depth;
+ int depth;
private boolean advance;
@@ -573,18 +574,13 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
* @param p
* an iterator to walk over. The iterator should be new, with no
* parent, and should still be positioned before the first entry.
- * The tree which the iterator operates on must have the same root
- * as other trees in the walk.
- *
+ * The tree which the iterator operates on must have the same
+ * root as other trees in the walk.
* @return position of this tree within the walker.
- * @throws CorruptObjectException
- * the iterator was unable to obtain its first entry, due to
- * possible data corruption within the backing data store.
*/
- public int addTree(final AbstractTreeIterator p)
- throws CorruptObjectException {
- final int n = trees.length;
- final AbstractTreeIterator[] newTrees = new AbstractTreeIterator[n + 1];
+ public int addTree(AbstractTreeIterator p) {
+ int n = trees.length;
+ AbstractTreeIterator[] newTrees = new AbstractTreeIterator[n + 1];
System.arraycopy(trees, 0, newTrees, 0, n);
newTrees[n] = p;
@@ -665,13 +661,30 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
return true;
}
} catch (StopWalkException stop) {
- for (final AbstractTreeIterator t : trees)
- t.stopWalk();
+ stopWalk();
return false;
}
}
/**
+ * Notify iterators the walk is aborting.
+ * <p>
+ * Primarily to notify {@link DirCacheBuildIterator} the walk is aborting so
+ * that it can copy any remaining entries.
+ *
+ * @throws IOException
+ * if traversal of remaining entries throws an exception during
+ * object access. This should never occur as remaining trees
+ * should already be in memory, however the methods used to
+ * finish traversal are declared to throw IOException.
+ */
+ void stopWalk() throws IOException {
+ for (AbstractTreeIterator t : trees) {
+ t.stopWalk();
+ }
+ }
+
+ /**
* Obtain the tree iterator for the current entry.
* <p>
* Entering into (or exiting out of) a subtree causes the current tree
@@ -861,10 +874,13 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
* Test if the supplied path matches the current entry's path.
* <p>
* This method tests that the supplied path is exactly equal to the current
- * entry, or is one of its parent directories. It is faster to use this
+ * entry or is one of its parent directories. It is faster to use this
* method then to use {@link #getPathString()} to first create a String
* object, then test <code>startsWith</code> or some other type of string
* match function.
+ * <p>
+ * If the current entry is a subtree, then all paths within the subtree
+ * are considered to match it.
*
* @param p
* path buffer to test. Callers should ensure the path does not
@@ -900,7 +916,7 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
// If p[ci] == '/' then pattern matches this subtree,
// otherwise we cannot be certain so we return -1.
//
- return p[ci] == '/' ? 0 : -1;
+ return p[ci] == '/' && FileMode.TREE.equals(t.mode) ? 0 : -1;
}
// Both strings are identical.
@@ -1062,7 +1078,7 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
}
}
- private void exitSubtree() {
+ void exitSubtree() {
depth--;
for (int i = 0; i < trees.length; i++)
trees[i] = trees[i].parent;
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 94beeeb56f..0d617ee7f9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -89,6 +89,7 @@ import org.eclipse.jgit.submodule.SubmoduleWalk;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FS.ExecutionResult;
import org.eclipse.jgit.util.IO;
+import org.eclipse.jgit.util.Paths;
import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.io.EolCanonicalizingInputStream;
@@ -692,31 +693,13 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
}
private static final Comparator<Entry> ENTRY_CMP = new Comparator<Entry>() {
- public int compare(final Entry o1, final Entry o2) {
- final byte[] a = o1.encodedName;
- final byte[] b = o2.encodedName;
- final int aLen = o1.encodedNameLen;
- final int bLen = o2.encodedNameLen;
- int cPos;
-
- for (cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
- final int cmp = (a[cPos] & 0xff) - (b[cPos] & 0xff);
- if (cmp != 0)
- return cmp;
- }
-
- if (cPos < aLen)
- return (a[cPos] & 0xff) - lastPathChar(o2);
- if (cPos < bLen)
- return lastPathChar(o1) - (b[cPos] & 0xff);
- return lastPathChar(o1) - lastPathChar(o2);
+ public int compare(Entry a, Entry b) {
+ return Paths.compare(
+ a.encodedName, 0, a.encodedNameLen, a.getMode().getBits(),
+ b.encodedName, 0, b.encodedNameLen, b.getMode().getBits());
}
};
- static int lastPathChar(final Entry e) {
- return e.getMode() == FileMode.TREE ? '/' : '\0';
- }
-
/**
* Constructor helper.
*
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java
index bdfde0bfcd..7601956c43 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilterGroup.java
@@ -245,9 +245,9 @@ public class PathFilterGroup {
int hash = hasher.nextHash();
if (fullpaths.contains(rp, hasher.length(), hash))
return true;
- if (!hasher.hasNext())
- if (prefixes.contains(rp, hasher.length(), hash))
- return true;
+ if (!hasher.hasNext() && walker.isSubtree()
+ && prefixes.contains(rp, hasher.length(), hash))
+ return true;
}
final int cmp = walker.isPathPrefix(max, max.length);
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 35fc99e54e..e14096e598 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java
@@ -42,7 +42,6 @@
*/
package org.eclipse.jgit.util;
-import java.io.IOException;
import java.util.regex.Pattern;
import org.eclipse.jgit.lib.Constants;
@@ -90,12 +89,10 @@ public class ChangeIdUtil {
* The commit message
* @return the change id SHA1 string (without the 'I') or null if the
* message is not complete enough
- * @throws IOException
*/
public static ObjectId computeChangeId(final ObjectId treeId,
final ObjectId firstParentId, final PersonIdent author,
- final PersonIdent committer, final String message)
- throws IOException {
+ final PersonIdent committer, final String message) {
String cleanMessage = clean(message);
if (cleanMessage.length() == 0)
return null;
@@ -116,8 +113,7 @@ public class ChangeIdUtil {
b.append("\n\n"); //$NON-NLS-1$
b.append(cleanMessage);
try (ObjectInserter f = new ObjectInserter.Formatter()) {
- return f.idFor(Constants.OBJ_COMMIT, //
- b.toString().getBytes(Constants.CHARACTER_ENCODING));
+ return f.idFor(Constants.OBJ_COMMIT, Constants.encode(b.toString()));
}
}
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 727ea79cc9..aa101f73f9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java
@@ -409,7 +409,9 @@ public class FileUtils {
throws IOException {
Path nioPath = path.toPath();
if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) {
- if (Files.isRegularFile(nioPath)) {
+ BasicFileAttributes attrs = Files.readAttributes(nioPath,
+ BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
+ if (attrs.isRegularFile() || attrs.isSymbolicLink()) {
delete(path);
} else {
delete(path, EMPTY_DIRECTORIES_ONLY | RECURSIVE);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java
new file mode 100644
index 0000000000..6be7ddbe12
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/Paths.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2016, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.util;
+
+import static org.eclipse.jgit.lib.FileMode.TYPE_MASK;
+import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
+
+/**
+ * Utility functions for paths inside of a Git repository.
+ *
+ * @since 4.2
+ */
+public class Paths {
+ /**
+ * Remove trailing {@code '/'} if present.
+ *
+ * @param path
+ * input path to potentially remove trailing {@code '/'} from.
+ * @return null if {@code path == null}; {@code path} after removing a
+ * trailing {@code '/'}.
+ */
+ public static String stripTrailingSeparator(String path) {
+ if (path == null || path.isEmpty()) {
+ return path;
+ }
+
+ int i = path.length();
+ if (path.charAt(path.length() - 1) != '/') {
+ return path;
+ }
+ do {
+ i--;
+ } while (path.charAt(i - 1) == '/');
+ return path.substring(0, i);
+ }
+
+ /**
+ * Compare two paths according to Git path sort ordering rules.
+ *
+ * @param aPath
+ * first path buffer. The range {@code [aPos, aEnd)} is used.
+ * @param aPos
+ * index into {@code aPath} where the first path starts.
+ * @param aEnd
+ * 1 past last index of {@code aPath}.
+ * @param aMode
+ * mode of the first file. Trees are sorted as though
+ * {@code aPath[aEnd] == '/'}, even if aEnd does not exist.
+ * @param bPath
+ * second path buffer. The range {@code [bPos, bEnd)} is used.
+ * @param bPos
+ * index into {@code bPath} where the second path starts.
+ * @param bEnd
+ * 1 past last index of {@code bPath}.
+ * @param bMode
+ * mode of the second file. Trees are sorted as though
+ * {@code bPath[bEnd] == '/'}, even if bEnd does not exist.
+ * @return &lt;0 if {@code aPath} sorts before {@code bPath};
+ * 0 if the paths are the same;
+ * &gt;0 if {@code aPath} sorts after {@code bPath}.
+ */
+ public static int compare(byte[] aPath, int aPos, int aEnd, int aMode,
+ byte[] bPath, int bPos, int bEnd, int bMode) {
+ int cmp = coreCompare(
+ aPath, aPos, aEnd, aMode,
+ bPath, bPos, bEnd, bMode);
+ if (cmp == 0) {
+ cmp = lastPathChar(aMode) - lastPathChar(bMode);
+ }
+ return cmp;
+ }
+
+ /**
+ * Compare two paths, checking for identical name.
+ * <p>
+ * Unlike {@code compare} this method returns {@code 0} when the paths have
+ * the same characters in their names, even if the mode differs. It is
+ * intended for use in validation routines detecting duplicate entries.
+ * <p>
+ * Returns {@code 0} if the names are identical and a conflict exists
+ * between {@code aPath} and {@code bPath}, as they share the same name.
+ * <p>
+ * Returns {@code <0} if all possibles occurrences of {@code aPath} sort
+ * before {@code bPath} and no conflict can happen. In a properly sorted
+ * tree there are no other occurrences of {@code aPath} and therefore there
+ * are no duplicate names.
+ * <p>
+ * Returns {@code >0} when it is possible for a duplicate occurrence of
+ * {@code aPath} to appear later, after {@code bPath}. Callers should
+ * continue to examine candidates for {@code bPath} until the method returns
+ * one of the other return values.
+ *
+ * @param aPath
+ * first path buffer. The range {@code [aPos, aEnd)} is used.
+ * @param aPos
+ * index into {@code aPath} where the first path starts.
+ * @param aEnd
+ * 1 past last index of {@code aPath}.
+ * @param bPath
+ * second path buffer. The range {@code [bPos, bEnd)} is used.
+ * @param bPos
+ * index into {@code bPath} where the second path starts.
+ * @param bEnd
+ * 1 past last index of {@code bPath}.
+ * @param bMode
+ * mode of the second file. Trees are sorted as though
+ * {@code bPath[bEnd] == '/'}, even if bEnd does not exist.
+ * @return &lt;0 if no duplicate name could exist;
+ * 0 if the paths have the same name;
+ * &gt;0 other {@code bPath} should still be checked by caller.
+ */
+ public static int compareSameName(
+ byte[] aPath, int aPos, int aEnd,
+ byte[] bPath, int bPos, int bEnd, int bMode) {
+ return coreCompare(
+ aPath, aPos, aEnd, TYPE_TREE,
+ bPath, bPos, bEnd, bMode);
+ }
+
+ private static int coreCompare(
+ byte[] aPath, int aPos, int aEnd, int aMode,
+ byte[] bPath, int bPos, int bEnd, int bMode) {
+ while (aPos < aEnd && bPos < bEnd) {
+ int cmp = (aPath[aPos++] & 0xff) - (bPath[bPos++] & 0xff);
+ if (cmp != 0) {
+ return cmp;
+ }
+ }
+ if (aPos < aEnd) {
+ return (aPath[aPos] & 0xff) - lastPathChar(bMode);
+ }
+ if (bPos < bEnd) {
+ return lastPathChar(aMode) - (bPath[bPos] & 0xff);
+ }
+ return 0;
+ }
+
+ private static int lastPathChar(int mode) {
+ if ((mode & TYPE_MASK) == TYPE_TREE) {
+ return '/';
+ }
+ return 0;
+ }
+
+ private Paths() {
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
index 45c339fb48..f2955f7e6b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/RawParseUtils.java
@@ -44,6 +44,8 @@
package org.eclipse.jgit.util;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.ObjectChecker.author;
import static org.eclipse.jgit.lib.ObjectChecker.committer;
import static org.eclipse.jgit.lib.ObjectChecker.encoding;
@@ -60,6 +62,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.PersonIdent;
@@ -70,7 +73,7 @@ public final class RawParseUtils {
*
* @since 2.2
*/
- public static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); //$NON-NLS-1$
+ public static final Charset UTF8_CHARSET = UTF_8;
private static final byte[] digits10;
@@ -81,8 +84,9 @@ public final class RawParseUtils {
private static final Map<String, Charset> encodingAliases;
static {
- encodingAliases = new HashMap<String, Charset>();
- encodingAliases.put("latin-1", Charset.forName("ISO-8859-1")); //$NON-NLS-1$ //$NON-NLS-2$
+ encodingAliases = new HashMap<>();
+ encodingAliases.put("latin-1", ISO_8859_1); //$NON-NLS-1$
+ encodingAliases.put("iso-latin-1", ISO_8859_1); //$NON-NLS-1$
digits10 = new byte['9' + 1];
Arrays.fill(digits10, (byte) -1);
@@ -671,35 +675,60 @@ public final class RawParseUtils {
}
/**
+ * Parse the "encoding " header as a string.
+ * <p>
+ * Locates the "encoding " header (if present) and returns its value.
+ *
+ * @param b
+ * buffer to scan.
+ * @return the encoding header as specified in the commit; null if the
+ * header was not present and should be assumed.
+ * @since 4.2
+ */
+ @Nullable
+ public static String parseEncodingName(final byte[] b) {
+ int enc = encoding(b, 0);
+ if (enc < 0) {
+ return null;
+ }
+ int lf = nextLF(b, enc);
+ return decode(UTF_8, b, enc, lf - 1);
+ }
+
+ /**
* Parse the "encoding " header into a character set reference.
* <p>
* Locates the "encoding " header (if present) by first calling
* {@link #encoding(byte[], int)} and then returns the proper character set
* to apply to this buffer to evaluate its contents as character data.
* <p>
- * If no encoding header is present, {@link Constants#CHARSET} is assumed.
+ * If no encoding header is present {@code UTF-8} is assumed.
*
* @param b
* buffer to scan.
* @return the Java character set representation. Never null.
+ * @throws IllegalCharsetNameException
+ * if the character set requested by the encoding header is
+ * malformed and unsupportable.
+ * @throws UnsupportedCharsetException
+ * if the JRE does not support the character set requested by
+ * the encoding header.
*/
public static Charset parseEncoding(final byte[] b) {
- final int enc = encoding(b, 0);
- if (enc < 0)
- return Constants.CHARSET;
- final int lf = nextLF(b, enc);
- String decoded = decode(Constants.CHARSET, b, enc, lf - 1);
+ String enc = parseEncodingName(b);
+ if (enc == null) {
+ return UTF_8;
+ }
+
+ String name = enc.trim();
try {
- return Charset.forName(decoded);
- } catch (IllegalCharsetNameException badName) {
- Charset aliased = charsetForAlias(decoded);
- if (aliased != null)
- return aliased;
- throw badName;
- } catch (UnsupportedCharsetException badName) {
- Charset aliased = charsetForAlias(decoded);
- if (aliased != null)
+ return Charset.forName(name);
+ } catch (IllegalCharsetNameException
+ | UnsupportedCharsetException badName) {
+ Charset aliased = charsetForAlias(name);
+ if (aliased != null) {
return aliased;
+ }
throw badName;
}
}
@@ -738,7 +767,15 @@ public final class RawParseUtils {
* parsed.
*/
public static PersonIdent parsePersonIdent(final byte[] raw, final int nameB) {
- final Charset cs = parseEncoding(raw);
+ Charset cs;
+ try {
+ cs = parseEncoding(raw);
+ } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
+ // Assume UTF-8 for person identities, usually this is correct.
+ // If not decode() will fall back to the ISO-8859-1 encoding.
+ cs = UTF_8;
+ }
+
final int emailB = nextLF(raw, nameB, '<');
final int emailE = nextLF(raw, emailB, '>');
if (emailB >= raw.length || raw[emailB] == '\n' ||
@@ -886,7 +923,7 @@ public final class RawParseUtils {
*/
public static String decode(final byte[] buffer, final int start,
final int end) {
- return decode(Constants.CHARSET, buffer, start, end);
+ return decode(UTF_8, buffer, start, end);
}
/**
@@ -960,23 +997,21 @@ public final class RawParseUtils {
public static String decodeNoFallback(final Charset cs,
final byte[] buffer, final int start, final int end)
throws CharacterCodingException {
- final ByteBuffer b = ByteBuffer.wrap(buffer, start, end - start);
+ ByteBuffer b = ByteBuffer.wrap(buffer, start, end - start);
b.mark();
// Try our built-in favorite. The assumption here is that
// decoding will fail if the data is not actually encoded
// using that encoder.
- //
try {
- return decode(b, Constants.CHARSET);
+ return decode(b, UTF_8);
} catch (CharacterCodingException e) {
b.reset();
}
- if (!cs.equals(Constants.CHARSET)) {
+ if (!cs.equals(UTF_8)) {
// Try the suggested encoding, it might be right since it was
// provided by the caller.
- //
try {
return decode(b, cs);
} catch (CharacterCodingException e) {
@@ -986,9 +1021,8 @@ public final class RawParseUtils {
// Try the default character set. A small group of people
// might actually use the same (or very similar) locale.
- //
- final Charset defcs = Charset.defaultCharset();
- if (!defcs.equals(cs) && !defcs.equals(Constants.CHARSET)) {
+ Charset defcs = Charset.defaultCharset();
+ if (!defcs.equals(cs) && !defcs.equals(UTF_8)) {
try {
return decode(b, defcs);
} catch (CharacterCodingException e) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java
index 24b8b53330..8d39a22ac2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/StreamCopyThread.java
@@ -47,6 +47,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
+import java.util.concurrent.atomic.AtomicInteger;
/** Thread to copy from an input stream to an output stream. */
public class StreamCopyThread extends Thread {
@@ -58,6 +59,8 @@ public class StreamCopyThread extends Thread {
private volatile boolean done;
+ private final AtomicInteger flushCount = new AtomicInteger(0);
+
/**
* Create a thread to copy data from an input stream to an output stream.
*
@@ -82,6 +85,7 @@ public class StreamCopyThread extends Thread {
* the request.
*/
public void flush() {
+ flushCount.incrementAndGet();
interrupt();
}
@@ -109,22 +113,30 @@ public class StreamCopyThread extends Thread {
public void run() {
try {
final byte[] buf = new byte[BUFFER_SIZE];
- int interruptCounter = 0;
+ int flushCountBeforeRead = 0;
+ boolean readInterrupted = false;
for (;;) {
try {
- if (interruptCounter > 0) {
+ if (readInterrupted) {
dst.flush();
- interruptCounter--;
+ readInterrupted = false;
+ if (!flushCount.compareAndSet(flushCountBeforeRead, 0)) {
+ // There was a flush() call since last blocked read.
+ // Set interrupt status, so next blocked read will throw
+ // an InterruptedIOException and we will flush again.
+ interrupt();
+ }
}
if (done)
break;
+ flushCountBeforeRead = flushCount.get();
final int n;
try {
n = src.read(buf);
} catch (InterruptedIOException wakey) {
- interruptCounter++;
+ readInterrupted = true;
continue;
}
if (n < 0)
@@ -141,7 +153,7 @@ public class StreamCopyThread extends Thread {
// set interrupt status, which will be checked
// when we block in src.read
- if (writeInterrupted)
+ if (writeInterrupted || flushCount.get() > 0)
interrupt();
break;
}

Back to the top