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 27d322079..b75738c90 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
@@ -50,9 +50,11 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
+import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.junit.Test;
@@ -213,6 +215,45 @@ public class RepoCommandTest extends RepositoryTestCase {
assertEquals("The destination file has expected content", "world", content);
}
+ @Test
+ public void testBareRepo() throws Exception {
+ Repository remoteDb = createBareRepository();
+ Repository tempDb = createWorkRepository();
+ StringBuilder xmlContent = new StringBuilder();
+ xmlContent.append("\n")
+ .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("testBareRepo");
+ CloneCommand clone = Git.cloneRepository();
+ clone.setDirectory(directory);
+ clone.setURI(remoteDb.getDirectory().toURI().toString());
+ Repository localDb = clone.call().getRepository();
+ // The .gitmodules file should exist
+ File gitmodules = new File(localDb.getWorkTree(), ".gitmodules");
+ assertTrue("The .gitmodules file exists", gitmodules.exists());
+ // The first line of .gitmodules file should be expected
+ BufferedReader reader = new BufferedReader(new FileReader(gitmodules));
+ String content = reader.readLine();
+ reader.close();
+ assertEquals("The first line of .gitmodules file is as expected.",
+ "[submodule \"foo\"]", content);
+ // The gitlink should be the same of remote head sha1
+ String gitlink = localDb.resolve(Constants.HEAD + ":foo").name();
+ String remote = defaultDb.resolve(Constants.HEAD).name();
+ assertEquals("The gitlink is same as remote head", remote, gitlink);
+ }
+
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/gitrepo/internal/RepoText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties
index 29aa51ccd..33fdc2e15 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties
@@ -1,5 +1,6 @@
copyFileFailed=Error occurred during execution of copyfile rule.
errorNoDefault=Error: no default remote in file {0}.
errorParsingManifestFile=Error occurred during parsing manifest file {0}.
+errorRemoteUnavailable=Error remote {0} is unavailable.
invalidManifest=Invalid manifest.
repoCommitMessage=Added repo manifest.
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 475fbacaf..2766d7531 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -48,9 +48,11 @@ 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;
+import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -58,15 +60,32 @@ 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;
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.gitrepo.internal.RepoText;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+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.PersonIdent;
import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
@@ -89,10 +108,46 @@ public class RepoCommand extends GitCommand {
private String path;
private String uri;
private String groups;
+ private PersonIdent author;
+ private RemoteReader callback;
+ private List bareProjects;
private Git git;
private ProgressMonitor monitor;
+ /**
+ * A callback to get head 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.
+ */
+ public interface RemoteReader {
+ /**
+ * Read a remote repository's HEAD sha1.
+ *
+ * @param uri
+ * The URI of the remote repository
+ * @return the sha1 of the HEAD of the remote repository
+ */
+ public ObjectId sha1(String uri) throws GitAPIException;
+ }
+
+ /** A default implementation of {@link RemoteReader} callback. */
+ public static class DefaultRemoteReader implements RemoteReader {
+ public ObjectId sha1(String uri) throws GitAPIException {
+ Collection[ refs = Git
+ .lsRemoteRepository()
+ .setRemote(uri)
+ .call();
+ for (Ref ref : refs) {
+ if (Constants.HEAD.equals(ref.getName()))
+ return ref.getObjectId();
+ }
+ return null;
+ }
+ }
+
private static class CopyFile {
final String src;
final String dest;
@@ -293,6 +348,12 @@ public class RepoCommand extends GitCommand {
}
}
+ private static class RemoteUnavailableException extends GitAPIException {
+ RemoteUnavailableException(String uri, Throwable cause) {
+ super(MessageFormat.format(RepoText.get().errorRemoteUnavailable, uri), cause);
+ }
+ }
+
/**
* @param repo
*/
@@ -347,6 +408,32 @@ public class RepoCommand extends GitCommand {
return this;
}
+ /**
+ * Set the author/committer for the bare repository commit.
+ *
+ * For non-bare repositories, the current user will be used and this will be ignored.
+ *
+ * @param author
+ * @return this command
+ */
+ public RepoCommand setAuthor(final PersonIdent author) {
+ this.author = author;
+ return this;
+ }
+
+ /**
+ * Set the GetHeadFromUri callback.
+ *
+ * This is only used in bare repositories.
+ *
+ * @param callback
+ * @return this command
+ */
+ public RepoCommand setRemoteReader(final RemoteReader callback) {
+ this.callback = callback;
+ return this;
+ }
+
@Override
public RevCommit call() throws GitAPIException {
checkCallable();
@@ -355,7 +442,15 @@ public class RepoCommand extends GitCommand {
if (uri == null || uri.length() == 0)
throw new IllegalArgumentException(JGitText.get().uriNotConfigured);
- git = new Git(repo);
+ if (repo.isBare()) {
+ bareProjects = new ArrayList();
+ if (author == null)
+ author = new PersonIdent(repo);
+ if (callback == null)
+ callback = new DefaultRemoteReader();
+ } else
+ git = new Git(repo);
+
XmlManifest manifest = new XmlManifest(this, path, uri, groups);
try {
manifest.read();
@@ -363,23 +458,115 @@ public class RepoCommand extends GitCommand {
throw new ManifestErrorException(e);
}
- return git
- .commit()
- .setMessage(RepoText.get().repoCommitMessage)
- .call();
+ if (repo.isBare()) {
+ DirCache index = DirCache.newInCore();
+ DirCacheBuilder builder = index.builder();
+ ObjectInserter inserter = repo.newObjectInserter();
+ RevWalk rw = new RevWalk(repo);
+
+ try {
+ Config cfg = new Config();
+ for (Project proj : bareProjects) {
+ String name = proj.path;
+ String uri = proj.name;
+ 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);
+ ObjectId objectId;
+ try {
+ objectId = callback.sha1(uri);
+ } catch (GitAPIException e) {
+ // Something wrong getting the head sha1
+ throw new RemoteUnavailableException(uri, e);
+ } catch (IllegalArgumentException e) {
+ // The revision from the manifest is malformed.
+ throw new ManifestErrorException(e);
+ }
+ if (objectId == null)
+ throw new RemoteUnavailableException(uri, null);
+ dcEntry.setObjectId(objectId);
+ dcEntry.setFileMode(FileMode.GITLINK);
+ builder.add(dcEntry);
+ }
+ String content = cfg.toText();
+
+ // create a new DirCacheEntry for .gitmodules file.
+ final DirCacheEntry dcEntry = new DirCacheEntry(Constants.DOT_GIT_MODULES);
+ ObjectId objectId = inserter.insert(Constants.OBJ_BLOB,
+ content.getBytes(Constants.CHARACTER_ENCODING));
+ dcEntry.setObjectId(objectId);
+ dcEntry.setFileMode(FileMode.REGULAR_FILE);
+ builder.add(dcEntry);
+
+ builder.finish();
+ ObjectId treeId = index.writeTree(inserter);
+
+ // Create a Commit object, populate it and write it
+ ObjectId headId = repo.resolve(Constants.HEAD + "^{commit}"); //$NON-NLS-1$
+ CommitBuilder commit = new CommitBuilder();
+ commit.setTreeId(treeId);
+ if (headId != null)
+ commit.setParentIds(headId);
+ commit.setAuthor(author);
+ commit.setCommitter(author);
+ commit.setMessage(RepoText.get().repoCommitMessage);
+
+ ObjectId commitId = inserter.insert(commit);
+ inserter.flush();
+
+ RefUpdate ru = repo.updateRef(Constants.HEAD);
+ ru.setNewObjectId(commitId);
+ ru.setExpectedOldObjectId(headId != null ? headId : ObjectId.zeroId());
+ Result rc = ru.update(rw);
+
+ switch (rc) {
+ case NEW:
+ case FORCED:
+ case FAST_FORWARD:
+ // Successful. Do nothing.
+ break;
+ case REJECTED:
+ case LOCK_FAILURE:
+ throw new ConcurrentRefUpdateException(
+ JGitText.get().couldNotLockHEAD, ru.getRef(),
+ rc);
+ default:
+ throw new JGitInternalException(MessageFormat.format(
+ JGitText.get().updatingRefFailed,
+ Constants.HEAD, commitId.name(), rc));
+ }
+
+ return rw.parseCommit(commitId);
+ } catch (IOException e) {
+ throw new ManifestErrorException(e);
+ } finally {
+ rw.release();
+ }
+ } else {
+ return git
+ .commit()
+ .setMessage(RepoText.get().repoCommitMessage)
+ .call();
+ }
}
private void addSubmodule(String url, String name) throws SAXException {
- SubmoduleAddCommand add = git
- .submoduleAdd()
- .setPath(name)
- .setURI(url);
- if (monitor != null)
- add.setProgressMonitor(monitor);
- try {
- add.call();
- } catch (GitAPIException e) {
- throw new SAXException(e);
+ if (repo.isBare()) {
+ Project proj = new Project(url, name, null);
+ bareProjects.add(proj);
+ } else {
+ SubmoduleAddCommand add = git
+ .submoduleAdd()
+ .setPath(name)
+ .setURI(url);
+ if (monitor != null)
+ add.setProgressMonitor(monitor);
+ try {
+ add.call();
+ } catch (GitAPIException e) {
+ throw new SAXException(e);
+ }
}
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java
index 1313fff0d..34db723b6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java
@@ -62,6 +62,7 @@ public class RepoText extends TranslationBundle {
/***/ public String copyFileFailed;
/***/ public String errorNoDefault;
/***/ public String errorParsingManifestFile;
+ /***/ public String errorRemoteUnavailable;
/***/ public String invalidManifest;
/***/ public String repoCommitMessage;
}
]