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 51e2b6f65..f6dea74c3 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 @@ -193,7 +193,7 @@ public void testRepoManifestGroups() throws Exception { } @Test - public void testRepoManifestCopyfile() throws Exception { + public void testRepoManifestCopyFile() throws Exception { Repository localDb = createWorkRepository(); StringBuilder xmlContent = new StringBuilder(); xmlContent.append("\n") @@ -373,6 +373,44 @@ public void testRevisionBare() throws Exception { oldCommitId.name(), gitlink); } + @Test + public void testCopyFileBare() throws Exception { + Repository remoteDb = createBareRepository(); + Repository tempDb = createWorkRepository(); + StringBuilder xmlContent = new StringBuilder(); + xmlContent.append("\n") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append(""); + JGitTestUtil.writeTrashFile(tempDb, "manifest.xml", xmlContent.toString()); + RepoCommand command = new RepoCommand(remoteDb); + command.setPath(tempDb.getWorkTree().getAbsolutePath() + "/manifest.xml") + .setURI(rootUri) + .call(); + // Clone it + File directory = createTempDirectory("testCopyFileBare"); + CloneCommand clone = Git.cloneRepository(); + clone.setDirectory(directory); + clone.setURI(remoteDb.getDirectory().toURI().toString()); + Repository localDb = clone.call().getRepository(); + // The Hello file should exist + File hello = new File(localDb.getWorkTree(), "Hello"); + assertTrue("The Hello file exists", hello.exists()); + // The content of Hello file should be expected + BufferedReader reader = new BufferedReader(new FileReader(hello)); + String content = reader.readLine(); + reader.close(); + assertEquals("The Hello file has expected content", "branch world", content); + } + private void resolveRelativeUris() { // Find the longest common prefix ends with "/" as rootUri. defaultUri = defaultDb.getDirectory().toURI().toString(); diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index bb95fa85f..b3049b07f 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -51,6 +51,7 @@ cannotCreateConfig=cannot create config cannotCreateDirectory=Cannot create directory {0} cannotCreateHEAD=cannot create HEAD cannotCreateIndexfile=Cannot create an index file with name {0} +cannotCreateTempDir=Cannot create a temp dir cannotDeleteCheckedOutBranch=Branch {0} is checked out and can not be deleted cannotDeleteFile=Cannot delete file: {0} cannotDeleteStaleTrackingRef=Cannot delete stale tracking ref {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 b09129adb..f9893644d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java @@ -42,13 +42,13 @@ */ package org.eclipse.jgit.gitrepo; +import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.channels.FileChannel; -import java.nio.charset.Charset; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; @@ -59,8 +59,6 @@ import java.util.Map; import java.util.Set; -import org.eclipse.jgit.api.AddCommand; -import org.eclipse.jgit.api.LsRemoteCommand; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.GitCommand; import org.eclipse.jgit.api.SubmoduleAddCommand; @@ -78,6 +76,7 @@ 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; @@ -87,6 +86,7 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.util.FileUtils; import org.xml.sax.Attributes; import org.xml.sax.InputSource; @@ -121,8 +121,11 @@ public class RepoCommand extends GitCommand { * A callback to get ref sha1 of a repository from its uri. * * We provided a default implementation {@link DefaultRemoteReader} to - * use ls-remote command to read the sha1 from the repository. Callers may - * have their own quicker implementation. + * use ls-remote command to read the sha1 from the repository and clone the + * repository to read the file. Callers may have their own quicker + * implementation. + * + * @since 3.4 */ public interface RemoteReader { /** @@ -135,6 +138,20 @@ public interface RemoteReader { * @return the sha1 of the remote repository */ public ObjectId sha1(String uri, String ref) throws GitAPIException; + + /** + * Read a file from a remote repository. + * + * @param uri + * The URI of the remote repository + * @param ref + * The ref (branch/tag/etc.) to read + * @param path + * The relative path (inside the repo) to the file to read + * @return the file content. + */ + public byte[] readFile(String uri, String ref, String path) + throws GitAPIException, IOException; } /** A default implementation of {@link RemoteReader} callback. */ @@ -153,23 +170,50 @@ public ObjectId sha1(String uri, String ref) throws GitAPIException { Ref r = RefDatabase.findRef(map, ref); return r != null ? r.getObjectId() : null; } + + public byte[] readFile(String uri, String ref, String path) + throws GitAPIException, IOException { + File dir = FileUtils.createTempDir("jgit_", ".git", null); //$NON-NLS-1$ //$NON-NLS-2$ + Repository repo = Git + .cloneRepository() + .setBare(true) + .setDirectory(dir) + .setURI(uri) + .call() + .getRepository(); + ObjectReader reader = repo.newObjectReader(); + byte[] result; + try { + ObjectId oid = repo.resolve(ref + ":" + path); //$NON-NLS-1$ + result = reader.open(oid).getBytes(Integer.MAX_VALUE); + } finally { + reader.release(); + FileUtils.delete(dir, FileUtils.RECURSIVE); + } + return result; + } } private static class CopyFile { + final Repository repo; + final String path; final String src; final String dest; - final String relativeDest; CopyFile(Repository repo, String path, String src, String dest) { - this.src = repo.getWorkTree() + "/" + path + "/" + src; //$NON-NLS-1$ //$NON-NLS-2$ - this.relativeDest = dest; - this.dest = repo.getWorkTree() + "/" + dest; //$NON-NLS-1$ + this.repo = repo; + this.path = path; + this.src = src; + this.dest = dest; } void copy() throws IOException { - FileInputStream input = new FileInputStream(src); + File srcFile = new File(repo.getWorkTree(), + path + "/" + src); //$NON-NLS-1$ + File destFile = new File(repo.getWorkTree(), dest); + FileInputStream input = new FileInputStream(srcFile); try { - FileOutputStream output = new FileOutputStream(dest); + FileOutputStream output = new FileOutputStream(destFile); try { FileChannel channel = input.getChannel(); output.getChannel().transferFrom(channel, 0, channel.size()); @@ -185,9 +229,9 @@ void copy() throws IOException { private static class Project { final String name; final String path; + final String revision; final Set groups; final List copyfiles; - String revision; Project(String name, String path, String revision, String groups) { this.name = name; @@ -315,26 +359,11 @@ public void endDocument() throws SAXException { } for (Project proj : projects) { if (inGroups(proj)) { - if (proj.revision == null) - proj.revision = defaultRevision; - String url = remoteUrl + proj.name; - command.addSubmodule(url, proj.path, proj.revision); - for (CopyFile copyfile : proj.copyfiles) { - try { - copyfile.copy(); - } catch (IOException e) { - throw new SAXException( - RepoText.get().copyFileFailed, e); - } - AddCommand add = command.git - .add() - .addFilepattern(copyfile.relativeDest); - try { - add.call(); - } catch (GitAPIException e) { - throw new SAXException(e); - } - } + command.addSubmodule(remoteUrl + proj.name, + proj.path, + proj.revision == null ? + defaultRevision : proj.revision, + proj.copyfiles); } } } @@ -503,7 +532,7 @@ public RevCommit call() throws GitAPIException { cfg.setString("submodule", name, "path", name); //$NON-NLS-1$ //$NON-NLS-2$ cfg.setString("submodule", name, "url", uri); //$NON-NLS-1$ //$NON-NLS-2$ // create gitlink - final DirCacheEntry dcEntry = new DirCacheEntry(name); + DirCacheEntry dcEntry = new DirCacheEntry(name); ObjectId objectId; if (ObjectId.isId(proj.revision)) objectId = ObjectId.fromString(proj.revision); @@ -515,6 +544,16 @@ public RevCommit call() throws GitAPIException { dcEntry.setObjectId(objectId); dcEntry.setFileMode(FileMode.GITLINK); builder.add(dcEntry); + + for (CopyFile copyfile : proj.copyfiles) { + byte[] src = callback.readFile( + uri, proj.revision, copyfile.src); + objectId = inserter.insert(Constants.OBJ_BLOB, src); + dcEntry = new DirCacheEntry(copyfile.dest); + dcEntry.setObjectId(objectId); + dcEntry.setFileMode(FileMode.REGULAR_FILE); + builder.add(dcEntry); + } } String content = cfg.toText(); @@ -578,9 +617,11 @@ public RevCommit call() throws GitAPIException { } } - private void addSubmodule(String url, String name, String revision) throws SAXException { + private void addSubmodule(String url, String name, String revision, + List copyfiles) throws SAXException { if (repo.isBare()) { Project proj = new Project(url, name, revision, null); + proj.copyfiles.addAll(copyfiles); bareProjects.add(proj); } else { SubmoduleAddCommand add = git @@ -597,6 +638,10 @@ private void addSubmodule(String url, String name, String revision) throws SAXEx sub.checkout().setName(findRef(revision, subRepo)).call(); git.add().addFilepattern(name).call(); } + for (CopyFile copyfile : copyfiles) { + copyfile.copy(); + git.add().addFilepattern(copyfile.dest).call(); + } } catch (GitAPIException e) { throw new SAXException(e); } catch (IOException e) { 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 40e74ed89..576ba0f49 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -113,6 +113,7 @@ public static JGitText get() { /***/ public String cannotCreateDirectory; /***/ public String cannotCreateHEAD; /***/ public String cannotCreateIndexfile; + /***/ public String cannotCreateTempDir; /***/ public String cannotDeleteCheckedOutBranch; /***/ public String cannotDeleteFile; /***/ public String cannotDeleteStaleTrackingRef; 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 b77807c70..330cac154 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java @@ -362,4 +362,29 @@ public static void createSymLink(File path, String target) public static String readSymLink(File path) throws IOException { return FS.DETECTED.readSymLink(path); } + + /** + * Create a temporary directory. + * + * @param prefix + * @param suffix + * @param dir + * The parent dir, can be null to use system default temp dir. + * @return the temp dir created. + * @throws IOException + * @since 3.4 + */ + public static File createTempDir(String prefix, String suffix, File dir) + throws IOException { + final int RETRY = 1; // When something bad happens, retry once. + for (int i = 0; i < RETRY; i++) { + File tmp = File.createTempFile(prefix, suffix, dir); + if (!tmp.delete()) + continue; + if (!tmp.mkdir()) + continue; + return tmp; + } + throw new IOException(JGitText.get().cannotCreateTempDir); + } }