Browse Source

Handle repo submodules for bare repositories.

Change-Id: Id028a7bc9600baf0f3e2316a1f4b99e53ccc746a
Signed-off-by: Yuxuan 'fishy' Wang <fishywang@google.com>
stable-3.4
Yuxuan 'fishy' Wang 11 years ago
parent
commit
056135a148
  1. 41
      org.eclipse.jgit.test/tst/org/eclipse/jgit/gitrepo/RepoCommandTest.java
  2. 1
      org.eclipse.jgit/resources/org/eclipse/jgit/gitrepo/internal/RepoText.properties
  3. 217
      org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
  4. 1
      org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/internal/RepoText.java

41
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("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
.append("<manifest>")
.append("<remote name=\"remote1\" fetch=\".\" />")
.append("<default revision=\"master\" remote=\"remote1\" />")
.append("<project path=\"foo\" name=\"")
.append(defaultUri)
.append("\" />")
.append("</manifest>");
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();

1
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.

217
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<RevCommit> {
private String path;
private String uri;
private String groups;
private PersonIdent author;
private RemoteReader callback;
private List<Project> 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<Ref> 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<RevCommit> {
}
}
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<RevCommit> {
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<RevCommit> {
if (uri == null || uri.length() == 0)
throw new IllegalArgumentException(JGitText.get().uriNotConfigured);
git = new Git(repo);
if (repo.isBare()) {
bareProjects = new ArrayList<Project>();
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<RevCommit> {
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);
}
}
}
}

1
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;
}

Loading…
Cancel
Save