diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java index 022f6433d..c170ac1b3 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java @@ -56,13 +56,16 @@ import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; +import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.InvalidRemoteException; -import org.eclipse.jgit.api.errors.RefNotFoundException; +import org.eclipse.jgit.gitrepo.RepoCommand.RemoteFile; +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.BlobBasedConfig; @@ -74,6 +77,7 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FS; import org.junit.Test; @@ -142,6 +146,7 @@ public void setUp() throws Exception { static class IndexedRepos implements RepoCommand.RemoteReader { Map uriRepoMap; + IndexedRepos() { uriRepoMap = new HashMap<>(); } @@ -170,19 +175,21 @@ public ObjectId sha1(String uri, String refname) throws GitAPIException { } @Override - public byte[] readFile(String uri, String refName, String path) - throws GitAPIException, IOException { + public RemoteFile readFileWithMode(String uri, String ref, String path) + throws GitAPIException, IOException { Repository repo = uriRepoMap.get(uri); + ObjectId refCommitId = sha1(uri, ref); + if (refCommitId == null) { + throw new InvalidRefNameException(MessageFormat + .format(JGitText.get().refNotResolved, ref)); + } + RevCommit commit = repo.parseCommit(refCommitId); + TreeWalk tw = TreeWalk.forPath(repo, path, commit.getTree()); - String idStr = refName + ":" + path; - ObjectId id = repo.resolve(idStr); - if (id == null) { - throw new RefNotFoundException( - String.format("repo %s does not have %s", repo.toString(), idStr)); - } - try (ObjectReader reader = repo.newObjectReader()) { - return reader.open(id).getCachedBytes(Integer.MAX_VALUE); - } + // TODO(ifrade): Cope better with big files (e.g. using InputStream + // instead of byte[]) + return new RemoteFile(tw.getObjectReader().open(tw.getObjectId(0)) + .getCachedBytes(Integer.MAX_VALUE), tw.getFileMode(0)); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java index 5a73cdc06..e01101095 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java @@ -59,12 +59,14 @@ import java.util.StringJoiner; import java.util.TreeMap; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.GitCommand; import org.eclipse.jgit.api.SubmoduleAddCommand; import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; @@ -80,7 +82,6 @@ import org.eclipse.jgit.lib.FileMode; 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; @@ -90,6 +91,7 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FileUtils; /** @@ -144,7 +146,9 @@ public interface RemoteReader { * @param uri * The URI of the remote repository * @param ref - * The ref (branch/tag/etc.) to read + * Name of the ref to lookup. May be a short-hand form, e.g. + * "master" which is is automatically expanded to + * "refs/heads/master" if "refs/heads/master" already exists. * @return the sha1 of the remote repository, or null if the ref does * not exist. * @throws GitAPIException @@ -165,13 +169,93 @@ public interface RemoteReader { * @throws GitAPIException * @throws IOException * @since 3.5 + * + * @deprecated Use {@link #readFileWithMode(String, String, String)} + * instead */ - public byte[] readFile(String uri, String ref, String path) + @Deprecated + public default byte[] readFile(String uri, String ref, String path) + throws GitAPIException, IOException { + return readFileWithMode(uri, ref, path).getContents(); + } + + /** + * Read contents and mode (i.e. permissions) of the file from a remote + * repository. + * + * @param uri + * The URI of the remote repository + * @param ref + * Name of the ref to lookup. May be a short-hand form, e.g. + * "master" which is is automatically expanded to + * "refs/heads/master" if "refs/heads/master" already exists. + * @param path + * The relative path (inside the repo) to the file to read + * @return The contents and file mode of the file in the given + * repository and branch. Never null. + * @throws GitAPIException + * If the ref have an invalid or ambiguous name, or it does + * not exist in the repository, + * @throws IOException + * If the object does not exist or is too large + * @since 5.2 + */ + @NonNull + public RemoteFile readFileWithMode(String uri, String ref, String path) throws GitAPIException, IOException; } + /** + * Read-only view of contents and file mode (i.e. permissions) for a file in + * a remote repository. + * + * @since 5.2 + */ + public static final class RemoteFile { + @NonNull + private final byte[] contents; + + @NonNull + private final FileMode fileMode; + + /** + * @param contents + * Raw contents of the file. + * @param fileMode + * Git file mode for this file (e.g. executable or regular) + */ + public RemoteFile(@NonNull byte[] contents, + @NonNull FileMode fileMode) { + this.contents = Objects.requireNonNull(contents); + this.fileMode = Objects.requireNonNull(fileMode); + } + + /** + * Contents of the file. + *

+ * Callers who receive this reference must not modify its contents (as + * it can point to internal cached data). + * + * @return Raw contents of the file. Do not modify it. + */ + @NonNull + public byte[] getContents() { + return contents; + } + + /** + * @return Git file mode for this file (e.g. executable or regular) + */ + @NonNull + public FileMode getFileMode() { + return fileMode; + } + + } + /** A default implementation of {@link RemoteReader} callback. */ public static class DefaultRemoteReader implements RemoteReader { + @Override public ObjectId sha1(String uri, String ref) throws GitAPIException { Map map = Git @@ -183,38 +267,30 @@ public ObjectId sha1(String uri, String ref) throws GitAPIException { } @Override - public byte[] readFile(String uri, String ref, String path) + public RemoteFile readFileWithMode(String uri, String ref, String path) throws GitAPIException, IOException { File dir = FileUtils.createTempDir("jgit_", ".git", null); //$NON-NLS-1$ //$NON-NLS-2$ try (Git git = Git.cloneRepository().setBare(true).setDirectory(dir) .setURI(uri).call()) { - return readFileFromRepo(git.getRepository(), ref, path); + Repository repo = git.getRepository(); + ObjectId refCommitId = sha1(uri, ref); + if (refCommitId == null) { + throw new InvalidRefNameException(MessageFormat + .format(JGitText.get().refNotResolved, ref)); + } + RevCommit commit = repo.parseCommit(refCommitId); + TreeWalk tw = TreeWalk.forPath(repo, path, commit.getTree()); + + // TODO(ifrade): Cope better with big files (e.g. using + // InputStream instead of byte[]) + return new RemoteFile( + tw.getObjectReader().open(tw.getObjectId(0)) + .getCachedBytes(Integer.MAX_VALUE), + tw.getFileMode(0)); } finally { FileUtils.delete(dir, FileUtils.RECURSIVE); } } - - /** - * Read a file from the repository - * - * @param repo - * The repository containing the file - * @param ref - * The ref (branch/tag/etc.) to read - * @param path - * The relative path (inside the repo) to the file to read - * @return the file's content - * @throws GitAPIException - * @throws IOException - * @since 3.5 - */ - protected byte[] readFileFromRepo(Repository repo, - String ref, String path) throws GitAPIException, IOException { - try (ObjectReader reader = repo.newObjectReader()) { - ObjectId oid = repo.resolve(ref + ":" + path); //$NON-NLS-1$ - return reader.open(oid).getBytes(Integer.MAX_VALUE); - } - } } @SuppressWarnings("serial")