Handle repo submodules for bare repositories.
Change-Id: Id028a7bc9600baf0f3e2316a1f4b99e53ccc746a Signed-off-by: Yuxuan 'fishy' Wang <fishywang@google.com>
This commit is contained in:
parent
7f394cf162
commit
056135a148
|
@ -50,9 +50,11 @@
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.api.CloneCommand;
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.junit.JGitTestUtil;
|
import org.eclipse.jgit.junit.JGitTestUtil;
|
||||||
import org.eclipse.jgit.junit.RepositoryTestCase;
|
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||||
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -213,6 +215,45 @@ public void testRepoManifestCopyfile() throws Exception {
|
||||||
assertEquals("The destination file has expected content", "world", content);
|
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() {
|
private void resolveRelativeUris() {
|
||||||
// Find the longest common prefix ends with "/" as rootUri.
|
// Find the longest common prefix ends with "/" as rootUri.
|
||||||
defaultUri = defaultDb.getDirectory().toURI().toString();
|
defaultUri = defaultDb.getDirectory().toURI().toString();
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
copyFileFailed=Error occurred during execution of copyfile rule.
|
copyFileFailed=Error occurred during execution of copyfile rule.
|
||||||
errorNoDefault=Error: no default remote in file {0}.
|
errorNoDefault=Error: no default remote in file {0}.
|
||||||
errorParsingManifestFile=Error occurred during parsing manifest file {0}.
|
errorParsingManifestFile=Error occurred during parsing manifest file {0}.
|
||||||
|
errorRemoteUnavailable=Error remote {0} is unavailable.
|
||||||
invalidManifest=Invalid manifest.
|
invalidManifest=Invalid manifest.
|
||||||
repoCommitMessage=Added repo manifest.
|
repoCommitMessage=Added repo manifest.
|
||||||
|
|
|
@ -48,9 +48,11 @@
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -58,15 +60,32 @@
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.AddCommand;
|
import org.eclipse.jgit.api.AddCommand;
|
||||||
|
import org.eclipse.jgit.api.LsRemoteCommand;
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.api.GitCommand;
|
import org.eclipse.jgit.api.GitCommand;
|
||||||
import org.eclipse.jgit.api.SubmoduleAddCommand;
|
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.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.gitrepo.internal.RepoText;
|
||||||
import org.eclipse.jgit.internal.JGitText;
|
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.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.lib.Repository;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
|
||||||
import org.xml.sax.Attributes;
|
import org.xml.sax.Attributes;
|
||||||
import org.xml.sax.InputSource;
|
import org.xml.sax.InputSource;
|
||||||
|
@ -89,10 +108,46 @@ public class RepoCommand extends GitCommand<RevCommit> {
|
||||||
private String path;
|
private String path;
|
||||||
private String uri;
|
private String uri;
|
||||||
private String groups;
|
private String groups;
|
||||||
|
private PersonIdent author;
|
||||||
|
private RemoteReader callback;
|
||||||
|
|
||||||
|
private List<Project> bareProjects;
|
||||||
private Git git;
|
private Git git;
|
||||||
private ProgressMonitor monitor;
|
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 {
|
private static class CopyFile {
|
||||||
final String src;
|
final String src;
|
||||||
final String dest;
|
final String dest;
|
||||||
|
@ -293,6 +348,12 @@ private static class ManifestErrorException extends GitAPIException {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class RemoteUnavailableException extends GitAPIException {
|
||||||
|
RemoteUnavailableException(String uri, Throwable cause) {
|
||||||
|
super(MessageFormat.format(RepoText.get().errorRemoteUnavailable, uri), cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param repo
|
* @param repo
|
||||||
*/
|
*/
|
||||||
|
@ -347,6 +408,32 @@ public RepoCommand setProgressMonitor(final ProgressMonitor monitor) {
|
||||||
return this;
|
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
|
@Override
|
||||||
public RevCommit call() throws GitAPIException {
|
public RevCommit call() throws GitAPIException {
|
||||||
checkCallable();
|
checkCallable();
|
||||||
|
@ -355,7 +442,15 @@ public RevCommit call() throws GitAPIException {
|
||||||
if (uri == null || uri.length() == 0)
|
if (uri == null || uri.length() == 0)
|
||||||
throw new IllegalArgumentException(JGitText.get().uriNotConfigured);
|
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);
|
XmlManifest manifest = new XmlManifest(this, path, uri, groups);
|
||||||
try {
|
try {
|
||||||
manifest.read();
|
manifest.read();
|
||||||
|
@ -363,23 +458,115 @@ public RevCommit call() throws GitAPIException {
|
||||||
throw new ManifestErrorException(e);
|
throw new ManifestErrorException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return git
|
if (repo.isBare()) {
|
||||||
.commit()
|
DirCache index = DirCache.newInCore();
|
||||||
.setMessage(RepoText.get().repoCommitMessage)
|
DirCacheBuilder builder = index.builder();
|
||||||
.call();
|
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 {
|
private void addSubmodule(String url, String name) throws SAXException {
|
||||||
SubmoduleAddCommand add = git
|
if (repo.isBare()) {
|
||||||
.submoduleAdd()
|
Project proj = new Project(url, name, null);
|
||||||
.setPath(name)
|
bareProjects.add(proj);
|
||||||
.setURI(url);
|
} else {
|
||||||
if (monitor != null)
|
SubmoduleAddCommand add = git
|
||||||
add.setProgressMonitor(monitor);
|
.submoduleAdd()
|
||||||
try {
|
.setPath(name)
|
||||||
add.call();
|
.setURI(url);
|
||||||
} catch (GitAPIException e) {
|
if (monitor != null)
|
||||||
throw new SAXException(e);
|
add.setProgressMonitor(monitor);
|
||||||
|
try {
|
||||||
|
add.call();
|
||||||
|
} catch (GitAPIException e) {
|
||||||
|
throw new SAXException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ public static RepoText get() {
|
||||||
/***/ public String copyFileFailed;
|
/***/ public String copyFileFailed;
|
||||||
/***/ public String errorNoDefault;
|
/***/ public String errorNoDefault;
|
||||||
/***/ public String errorParsingManifestFile;
|
/***/ public String errorParsingManifestFile;
|
||||||
|
/***/ public String errorRemoteUnavailable;
|
||||||
/***/ public String invalidManifest;
|
/***/ public String invalidManifest;
|
||||||
/***/ public String repoCommitMessage;
|
/***/ public String repoCommitMessage;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue