package org.eclipse.buckminster.git.internal;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.Set;
import org.eclipse.buckminster.core.version.VersionMatch;
import org.eclipse.buckminster.core.version.VersionSelector;
import org.eclipse.buckminster.runtime.Buckminster;
import org.eclipse.buckminster.runtime.BuckminsterException;
import org.eclipse.buckminster.runtime.Logger;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.egit.core.RepositoryUtil;
import org.eclipse.jgit.api.CheckoutCommand;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.IndexDiff;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.osgi.util.NLS;
/**
* An instance of RepositoryAccess manages one component in some branch or tag
* in a repository.
*/
class RepositoryAccess {
private static final Logger logger = Buckminster.getLogger();
private static final Object NULL_STRING = "null"; //$NON-NLS-1$
private static String getGitTag(VersionMatch versionMatch) {
String tagName = getTagName(versionMatch);
if (tagName == null)
return null;
return Constants.R_TAGS + tagName;
}
private static String getTagName(VersionMatch versionMatch) {
if (versionMatch == null)
return null;
VersionSelector vs = versionMatch.getBranchOrTag();
if (vs != null && vs.getType() == VersionSelector.TAG && !vs.isDefault()) {
String tagName = vs.getName();
if (tagName.startsWith("refs/")) //$NON-NLS-1$
tagName = tagName.substring(5);
if (tagName.startsWith("tags/")) //$NON-NLS-1$
tagName = tagName.substring(5);
return tagName;
}
return null;
}
private final boolean autoFetch;
private final String component;
private final File localRepo;
private final String remoteName;
private Repository repository;
private final URIish repoURI;
RepositoryAccess(String fmt, Map<String, String> properties) throws CoreException {
int comma = fmt.lastIndexOf(',');
if (comma >= 0) {
component = fmt.substring(comma + 1);
fmt = fmt.substring(0, comma);
} else
component = null; // The repo _is_ the component
localRepo = new File(fmt, ".git"); //$NON-NLS-1$
if (!localRepo.isAbsolute())
throw BuckminsterException.fromMessage("Git repository path \"%s\" is not absolute", fmt); //$NON-NLS-1$
String remoteURIStr = properties.get(IPropertyKeys.PROP_REMOTE_URI);
if (remoteURIStr != null)
try {
if (remoteURIStr.startsWith("file:/") && remoteURIStr.length() > 6 && remoteURIStr.charAt(6) != '/') //$NON-NLS-1$
remoteURIStr = "file:///" + remoteURIStr.substring(6); //$NON-NLS-1$
URIish uri = new URIish(remoteURIStr);
String username = uri.getUser();
if (username != null) {
if (NULL_STRING.equals(username))
uri = uri.setUser(null);
String password = uri.getPass();
if (password != null && NULL_STRING.equals(password))
uri = uri.setPass(null);
}
repoURI = uri;
} catch (URISyntaxException e) {
throw BuckminsterException.wrap(e);
}
else
repoURI = null;
fmt = localRepo.getAbsolutePath();
if (!localRepo.exists()) {
if (repoURI == null)
throw BuckminsterException.fromMessage("Git repository path \"%s\" does not exist and value is provided for the \"%s\" property", //$NON-NLS-1$
fmt, IPropertyKeys.PROP_REMOTE_URI);
}
autoFetch = Boolean.parseBoolean(properties.get(IPropertyKeys.PROP_AUTO_FETCH));
String tmp = properties.get(IPropertyKeys.PROP_REMOTE_NAME);
if (tmp == null)
tmp = Constants.DEFAULT_REMOTE_NAME;
remoteName = tmp;
}
synchronized void close() {
if (repository != null) {
repository.close();
repository = null;
}
}
private String getBranchName(VersionMatch versionMatch) {
if (versionMatch != null) {
VersionSelector vs = versionMatch.getBranchOrTag();
if (vs != null && vs.getType() == VersionSelector.BRANCH && !vs.isDefault()) {
String branchName = vs.getName();
if (branchName.startsWith("refs/")) //$NON-NLS-1$
branchName = branchName.substring(5);
if (branchName.startsWith("remotes/")) //$NON-NLS-1$
branchName = branchName.substring(8);
else if (branchName.startsWith("heads/")) //$NON-NLS-1$
branchName = branchName.substring(6);
int rlen = remoteName.length();
if (branchName.length() > rlen && branchName.startsWith(remoteName) && branchName.charAt(rlen) == '/')
branchName = branchName.substring(rlen + 1);
return branchName;
}
}
return null;
}
RevCommit getBranchOrTagId(Repository repo, VersionMatch versionMatch, IProgressMonitor monitor) throws CoreException {
try {
RevCommit objId = null;
String revstr = getGitTag(versionMatch);
if (revstr != null) {
// Set the working copy to reflect a branch based on a tag
//
objId = parseCommit(repo.getRef(revstr));
if (objId == null)
throw BuckminsterException.fromMessage("Unable to obtain ObjectID for tag %s", revstr); //$NON-NLS-1$
// We don't want to simply check out the tag since that would
// create a detached HEAD. Instead
// we create a branch that originates from the tag. Unless that
// hasn't been done already
// of course.
//
try (Git git = new Git(repo)) {
CheckoutCommand co = git.checkout();
String tagBranch = "tag-branch_" + getTagName(versionMatch); //$NON-NLS-1$
objId = parseCommit(repo.getRef(tagBranch));
if (objId == null) {
logger.info("Checking out %s to new branch %s", revstr, tagBranch); //$NON-NLS-1$
co.setCreateBranch(true);
co.setName(tagBranch);
co.setStartPoint(revstr);
return parseCommit(co.call());
}
RevCommit currentId = parseCommit(repo.getRef(Constants.HEAD));
if (!currentId.equals(objId)) {
logger.info("Checking out existing branch %s to get tag %s", tagBranch, revstr); //$NON-NLS-1$
co.setCreateBranch(false);
co.setName(tagBranch);
RevCommit branchCommit = parseCommit(co.call());
if (!objId.equals(branchCommit)) {
// So what do we do now? If we reset this branch to
// the
// tag, we will
// loose commits.
logger.warning("Branch %s has moved since it was created from %s", tagBranch, revstr); //$NON-NLS-1$
}
}
}
return objId;
}
revstr = getGitBranch(versionMatch);
if (revstr == null)
revstr = Constants.HEAD;
objId = parseCommit(repo.getRef(revstr));
if (objId != null)
return objId;
String remoteBranch = getGitRemoteBranch(versionMatch, repo.getBranch());
objId = parseCommit(repo.getRef(remoteBranch));
if (objId == null)
throw BuckminsterException.fromMessage("Unable to obtain ObjectID for branch %s", remoteBranch); //$NON-NLS-1$
// We must create a new local branch that tracks the origin:
//
// git checkout --track -b <branch> origin/<branch>
//
String localBranch = getBranchName(versionMatch);
if (localBranch == null)
localBranch = Constants.MASTER;
logger.info("Creating branch %s to track remote branch %s", localBranch, remoteBranch); //$NON-NLS-1$
try (Git git = new Git(repo)) {
CheckoutCommand co = git.checkout();
co.setUpstreamMode(SetupUpstreamMode.TRACK);
co.setCreateBranch(true);
co.setName(localBranch);
co.setStartPoint(remoteBranch);
objId = parseCommit(co.call());
// Set up pull configuration
StoredConfig config = repo.getConfig();
config.setString("branch", localBranch, "remote", remoteName); //$NON-NLS-1$//$NON-NLS-2$
config.setString("branch", localBranch, "merge", Constants.R_HEADS + localBranch); //$NON-NLS-1$ //$NON-NLS-2$
config.save();
}
return objId;
} catch (Exception e) {
throw BuckminsterException.wrap(e);
}
}
String getComponent() {
return component;
}
private String getGitBranch(VersionMatch versionMatch) {
String branchName = getBranchName(versionMatch);
return branchName == null ? null : Constants.R_HEADS + branchName;
}
private String getGitRemoteBranch(VersionMatch versionMatch, String currentBranch) {
String branchName = getBranchName(versionMatch);
String remoteBase = remoteName + '/';
if (branchName == null)
branchName = currentBranch;
return remoteBase + branchName;
}
File getLocation(VersionMatch versionMatch) throws CoreException {
File location = getRepository(versionMatch, new NullProgressMonitor()).getWorkTree();
if (component != null)
location = new File(location, component);
return location;
}
Repository getRepository() throws CoreException {
File canonicalLocalRepo;
try {
canonicalLocalRepo = localRepo.getCanonicalFile();
} catch (IOException e) {
canonicalLocalRepo = localRepo;
}
String repoPath = canonicalLocalRepo.getAbsolutePath().intern();
synchronized (repoPath) {
if (repository != null)
return repository;
try {
boolean infant = !canonicalLocalRepo.exists();
if (infant) {
File localDir = canonicalLocalRepo.getParentFile();
logger.info("Cloning remote repository %s into %s", repoURI.toString(), localDir.getAbsolutePath()); //$NON-NLS-1$
CloneCommand cc = Git.cloneRepository();
cc.setBare(false);
cc.setDirectory(localDir);
cc.setNoCheckout(false);
cc.setURI(repoURI.toPrivateString());
cc.call();
}
repository = FileRepositoryBuilder.create(canonicalLocalRepo);
// Add repository if it's not already addded
RepositoryUtil repoUtil = org.eclipse.egit.core.Activator.getDefault().getRepositoryUtil();
if (repoUtil.addConfiguredRepository(canonicalLocalRepo))
logger.info("Added Git repository at %s to the set of known repositories", repoPath); //$NON-NLS-1$
return repository;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw BuckminsterException.wrap(e);
}
}
}
Repository getRepository(VersionMatch vm, IProgressMonitor monitor) throws CoreException {
String refStr = getGitTag(vm);
if (refStr == null)
refStr = getGitBranch(vm);
try {
Repository repo = getRepository();
synchronized (getRepositoryPath()) {
RevCommit wantedId = getBranchOrTagId(repo, vm, monitor);
RevCommit currentId = parseCommit(repo.getRef(Constants.HEAD));
if (refStr == null || wantedId.equals(currentId))
return repo;
IndexDiff diff = new IndexDiff(repo, refStr, new FileTreeIterator(repo));
if (!diff.diff())
return repo;
if (component != null) {
// We don't signal a conflict unless we find one beneath our
// own component
if (!(scanFiles(diff.getMissing()) || scanFiles(diff.getModified()) || scanFiles(diff.getUntracked())))
return repo;
}
}
throw BuckminsterException.fromMessage(NLS.bind(Messages.git_reader_0_cannot_switch_to_1_without_causing_conflict_beneath_2,
new Object[] { localRepo.getAbsolutePath(), refStr, component }));
} catch (IOException e) {
throw BuckminsterException.wrap(e);
}
}
String getRepositoryPath() {
File canonicalLocalRepo;
try {
canonicalLocalRepo = localRepo.getCanonicalFile();
} catch (IOException e) {
canonicalLocalRepo = localRepo;
}
return canonicalLocalRepo.getAbsolutePath().intern();
}
TreeWalk getTreeWalk(Repository repo, ObjectId id, String path, IProgressMonitor monitor) throws CoreException {
ObjectReader curs = repo.newObjectReader();
try {
CanonicalTreeParser p = new CanonicalTreeParser();
RevWalk revWalk = new RevWalk(curs);
try {
p.reset(curs, revWalk.parseTree(id));
} finally {
revWalk.close();
}
TreeWalk treeWalk = new TreeWalk(curs);
treeWalk.setRecursive(true);
treeWalk.addTree(p);
if (component == null) {
if (path == null) {
return treeWalk;
}
} else {
path = component + (path != null ? '/' + path : ""); //$NON-NLS-1$
}
treeWalk.setFilter(PathFilter.create(path));
return treeWalk;
} catch (Exception e) {
throw BuckminsterException.wrap(e);
} finally {
curs.close();
}
}
TreeWalk getTreeWalk(VersionMatch versionMatch, String path, IProgressMonitor monitor) throws CoreException {
ObjectId id = getBranchOrTagId(getRepository(versionMatch, monitor), versionMatch, monitor);
Repository repo = getRepository(versionMatch, monitor);
return getTreeWalk(repo, id, path, monitor);
}
boolean isAutoFetch() {
return autoFetch;
}
private RevCommit parseCommit(Ref branch) throws MissingObjectException, IncorrectObjectTypeException, IOException {
if (branch == null)
return null;
RevWalk rw = new RevWalk(repository);
try {
return rw.parseCommit(branch.getObjectId());
} finally {
rw.close();
}
}
boolean scanFiles(Set<String> files) {
if (files == null || files.isEmpty())
return false;
for (String file : files)
if (file.startsWith(component))
return true;
return false;
}
}