Java tutorial
/******************************************************************************* * Copyright (c) 2011 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ package org.jboss.tools.openshift.egit.core; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.egit.core.EclipseGitProgressTransformer; import org.eclipse.egit.core.IteratorService; import org.eclipse.egit.core.op.AddToIndexOperation; import org.eclipse.egit.core.op.CloneOperation; import org.eclipse.egit.core.op.CloneOperation.PostCloneTask; import org.eclipse.egit.core.op.CommitOperation; import org.eclipse.egit.core.op.ConnectProviderOperation; import org.eclipse.egit.core.op.FetchOperation; import org.eclipse.egit.core.op.MergeOperation; import org.eclipse.egit.core.op.PushOperation; import org.eclipse.egit.core.op.PushOperationResult; import org.eclipse.egit.core.op.PushOperationSpecification; import org.eclipse.egit.core.project.RepositoryMapping; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.InitCommand; import org.eclipse.jgit.api.MergeResult; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.lib.BranchTrackingStatus; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.IndexDiff; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.lib.UserConfig; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalkUtils; import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.transport.FetchResult; import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.Transport; import org.eclipse.jgit.transport.URIish; import org.eclipse.osgi.util.NLS; import org.eclipse.team.core.RepositoryProvider; import org.jboss.tools.openshift.egit.core.internal.EGitCoreActivator; import org.jboss.tools.openshift.egit.core.internal.utils.RegexUtils; /** * The Class EGitUtils. * * @author Andr Dietisheim */ public class EGitUtils { private static final int DEFAULT_TIMEOUT = 10 * 1024; // private static final RefSpec DEFAULT_PUSH_REF_SPEC = new RefSpec("refs/heads/*:refs/remotes/origin/*"); //$NON-NLS-1$ private static final String DEFAULT_REFSPEC_SOURCE = Constants.HEAD; // HEAD private static final String DEFAULT_REFSPEC_DESTINATION = Constants.R_HEADS + Constants.MASTER; // refs/heads/master private static final String EGIT_TEAM_PROVIDER_ID = "org.eclipse.egit.core.GitProvider"; private EGitUtils() { // inhibit instantiation } /** * Returns <code>true</code> if the given project is associated to any team * provider (git, svn, cvs, etc.). Returns <code>false</code> otherwise. * * @param project * the project to check * @return <code>true</code> if the project is associated with any team * provider. */ public static boolean isShared(IProject project) { return RepositoryProvider.getProvider(project) != null; } /** * Returns <code>true</code> if the given project is associated to the egit * team provider. Returns <code>false</code> otherwise. * * @param project * the project to check * @return <code>true</code> if the project is associated with the git team * provider. */ public static boolean isSharedWithGit(IProject project) { RepositoryProvider provider = RepositoryProvider.getProvider(project); return provider != null && EGIT_TEAM_PROVIDER_ID.equals(provider.getID()); } /** * Returns <code>true</code> if the given project exists and has a .git * folder in it. * * @param project * @return */ public static boolean hasDotGitFolder(IProject project) { if (project == null || !project.exists()) { return false; } return new File(project.getLocation().toOSString(), Constants.DOT_GIT).exists(); } /** * Shares the given project. A repository is created within the given * project and the project is connected to the freshly created repository. * * @param project * @param monitor * @return * @throws CoreException */ public static Repository share(IProject project, IProgressMonitor monitor) throws CoreException { Repository repository = createRepository(project, monitor); connect(project, repository, monitor); addToRepository(project, repository, monitor); commit(project, monitor); // checkout("master", repository); return repository; } /** * Creates a repository for the given project. The repository is created in * the .git directory within the given project. The project is not connected * with the new repository * * @param project * the project to create the repository for. * @param monitor * the monitor to report the progress to * @return * @throws CoreException * * @see #connect(IProject, Repository, IProgressMonitor) */ public static Repository createRepository(IProject project, IProgressMonitor monitor) throws CoreException { try { InitCommand init = Git.init(); init.setBare(false).setDirectory(project.getLocation().toFile()); Git git = init.call(); return git.getRepository(); } catch (JGitInternalException e) { throw new CoreException(EGitCoreActivator .createErrorStatus(NLS.bind("Could not initialize a git repository at {0}: {1}", getRepositoryPathFor(project), e.getMessage()), e)); } catch (GitAPIException e) { throw new CoreException(EGitCoreActivator .createErrorStatus(NLS.bind("Could not initialize a git repository at {0}: {1}", getRepositoryPathFor(project), e.getMessage()), e)); } } public static File getRepositoryPathFor(IProject project) { return new File(project.getLocationURI().getPath(), Constants.DOT_GIT); } public static void addToRepository(IProject project, Repository repository, IProgressMonitor monitor) throws CoreException { AddToIndexOperation add = new AddToIndexOperation(Collections.singletonList(project)); add.execute(monitor); } /** * Connects the given project to the repository within it. * * @param project * the project to connect * @param monitor * the monitor to report progress to * @throws CoreException */ public static void connect(IProject project, IProgressMonitor monitor) throws CoreException { connect(project, getRepositoryPathFor(project), monitor); } /** * Connects the given project to the given repository. * * @param project * the project to connect * @param repository * the repository to connect the project to * @param monitor * the monitor to report progress to * @throws CoreException */ private static void connect(IProject project, Repository repository, IProgressMonitor monitor) throws CoreException { connect(project, repository.getDirectory(), monitor); } private static void connect(IProject project, File repositoryFolder, IProgressMonitor monitor) throws CoreException { new ConnectProviderOperation(project, repositoryFolder).execute(monitor); } public static void cloneRepository(String uri, String remoteName, File destination, IProgressMonitor monitor) throws URISyntaxException, InvocationTargetException, InterruptedException { cloneRepository(uri, remoteName, destination, null, monitor); } public static void cloneRepository(String uri, String remoteName, File destination, PostCloneTask postCloneTask, IProgressMonitor monitor) throws URISyntaxException, InvocationTargetException, InterruptedException { URIish gitUri = new URIish(uri); CloneOperation cloneOperation = new CloneOperation(gitUri, true, null, destination, Constants.HEAD, remoteName, DEFAULT_TIMEOUT); if (postCloneTask != null) { cloneOperation.addPostCloneTask(postCloneTask); } cloneOperation.run(monitor); // RepositoryUtil repositoryUtil = // Activator.getDefault().getRepositoryUtil(); // repositoryUtil.addConfiguredRepository(new File(destination, // Constants.DOT_GIT)); } /** * Merges the given uri to HEAD in the given repository. The given branch is * the branch in the local repo the fetched HEAD is fetched to. * * @param branch * @param uri * @param repository * @param monitor * @throws CoreException * @throws InvocationTargetException */ public static void mergeWithRemote(URIish uri, String branch, Repository repository, IProgressMonitor monitor) throws CoreException, InvocationTargetException { RefSpec ref = new RefSpec().setSource(Constants.HEAD).setDestination(branch); fetch(uri, Collections.singletonList(ref), repository, monitor); merge(branch, repository, monitor); } /** * Merges current master with the given branch. The strategy used is Resolve * since egit does still not support the Recusive stragety. * * @param branch * the branch to merge * @param repository * the repository the branch is in * @param monitor * the monitor to report the progress to * @return the result of the merge * @throws CoreException * * @see MergeStrategy#RESOLVE * @link * http://www.eclipse.org/forums/index.php/mv/msg/261278/753913/#msg_753913 * @link https://bugs.eclipse.org/bugs/show_bug.cgi?id=354099 * @link https://bugs.eclipse.org/bugs/show_bug.cgi?id=359951 */ private static MergeResult merge(String branch, Repository repository, IProgressMonitor monitor) throws CoreException { MergeOperation merge = new MergeOperation(repository, branch, MergeStrategy.RESOLVE.getName()); merge.execute(monitor); return merge.getResult(); } /** * Fetches the source ref(s) (from the given ref spec(s)) from the given uri * to the given destination(s) (in the given ref spec(s)) to the given * repository. * * @param uri * the uri to fetch from * @param fetchRefsRefSpecs * the references with the sources and destinations * @param repository * the repository to fetch to * @param monitor * the monitor to report progress to * @return * @throws InvocationTargetException * @throws CoreException */ private static Collection<Ref> fetch(URIish uri, List<RefSpec> fetchRefsRefSpecs, Repository repository, IProgressMonitor monitor) throws InvocationTargetException, CoreException { FetchOperation fetch = new FetchOperation(repository, uri, fetchRefsRefSpecs, 10 * 1024, false); fetch.run(monitor); FetchResult result = fetch.getOperationResult(); return result.getAdvertisedRefs(); } /** * Commits the changes within the given project to it's configured * repository. The project has to be connected to a repository. * * @param project * the project whose changes shall be committed * @param monitor * the monitor to report progress to * @throws CoreException * * @see #connect(IProject, Repository) */ private static RevCommit commit(IProject project, String commitMessage, IProgressMonitor monitor) throws CoreException { Repository repository = getRepository(project); Assert.isLegal(repository != null, "Cannot commit project to repository. "); return commit(project, commitMessage, repository, monitor); } public static RevCommit commit(IProject project, IProgressMonitor monitor) throws CoreException { return commit(project, "Commit from JBoss Tools", monitor); } private static RevCommit commit(IProject project, String commitMessage, Repository repository, IProgressMonitor monitor) throws CoreException { Assert.isLegal(project != null, "Could not commit project. No project provided"); Assert.isLegal(repository != null, MessageFormat.format( "Could not commit. Project \"{0}\" is not connected to a repository (call #connect(project, repository) first)", project.getName())); /** * TODO: add capability to commit selectively */ UserConfig userConfig = getUserConfig(repository); CommitOperation op = new CommitOperation(null, null, null, getFormattedUser(userConfig.getAuthorName(), userConfig.getAuthorEmail()), getFormattedUser(userConfig.getCommitterName(), userConfig.getCommitterEmail()), commitMessage); op.setCommitAll(true); op.setRepository(repository); op.execute(monitor); return op.getCommit(); } /** * Returns the URIish's for the remote on the given project. * * @param remoteName * @param project * @return * @throws CoreException */ public static List<URIish> getRemoteURIs(String remoteName, IProject project) throws CoreException { List<URIish> uris = Collections.emptyList(); RemoteConfig remoteConfig = getRemoteByName(remoteName, getRepository(project)); if (remoteConfig != null) { uris = remoteConfig.getURIs(); } return uris; } public static List<URIish> getDefaultRemoteURIs(IProject project) throws CoreException { RemoteConfig remoteConfig = getRemoteConfig(getRepository(project)); if (remoteConfig != null) { return remoteConfig.getURIs(); } return new ArrayList<URIish>(); } /** * Returns all uris of alls remotes for the given project. * * @param project * the project to get all remotes and all uris from * @return all uris * @throws CoreException */ public static List<URIish> getAllRemoteURIs(IProject project) throws CoreException { List<RemoteConfig> remoteConfigs = getAllRemoteConfigs(getRepository(project)); List<URIish> uris = new ArrayList<URIish>(); if (remoteConfigs != null) { for (RemoteConfig remoteConfig : remoteConfigs) { uris.addAll(remoteConfig.getURIs()); } } return uris; } /** * Pushes the current branch of the given repository to the remote * repository that it originates from. * * @param repository * the repository that shall be pushed * @param monitor * the monitor to report progress to * @throws CoreException * core exception is thrown if the push could not be executed */ public static PushOperationResult push(Repository repository, IProgressMonitor monitor) throws CoreException { return push(repository, getRemoteConfig(repository), false, monitor); } /** * Pushes the given repository to the remote repo with the given name. * * @param remote * @param repository * @param monitor * @throws CoreException * * @see git config file: "[remote..." * @see #getAllRemoteConfigs(Repository) * @see RemoteConfig#getName() */ public static PushOperationResult push(String remote, Repository repository, IProgressMonitor monitor) throws CoreException { RemoteConfig remoteConfig = getRemoteByName(remote, repository); return push(repository, remoteConfig, false, monitor); } public static PushOperationResult pushForce(String remote, Repository repository, IProgressMonitor monitor) throws CoreException { RemoteConfig remoteConfig = getRemoteByName(remote, repository); return push(repository, remoteConfig, true, monitor); } private static PushOperationResult push(Repository repository, RemoteConfig remoteConfig, boolean force, IProgressMonitor monitor) throws CoreException { try { if (remoteConfig == null) { throw new CoreException(createStatus(null, "Repository \"{0}\" has no remote repository configured", repository.toString())); } PushOperation op = createPushOperation(remoteConfig, repository, force); op.run(monitor); PushOperationResult pushResult = op.getOperationResult(); if (hasFailedEntries(pushResult)) { throw new CoreException(EGitCoreActivator.createErrorStatus(NLS.bind( "Could not push repository {0}: {1}", repository.toString(), getErrors(pushResult)), null)); } return pushResult; } catch (CoreException e) { throw e; } catch (Exception e) { throw new CoreException(createStatus(e, "Could not push repo {0}", repository.toString())); } } private static String getErrors(PushOperationResult pushResult) { StringBuilder builder = new StringBuilder(); for (RemoteRefUpdate failedUpdate : getFailedUpdates(pushResult)) { builder.append(MessageFormat.format("push from {0} to {1} was {2}", failedUpdate.getSrcRef(), failedUpdate.getRemoteName(), failedUpdate.getStatus())); } return builder.toString(); } private static PushOperation createPushOperation(RemoteConfig remoteConfig, Repository repository, boolean force) throws CoreException { Collection<URIish> pushURIs = getPushURIs(remoteConfig); Collection<RefSpec> pushRefSpecs = createForceRefSpecs(force, getPushRefSpecs(remoteConfig)); PushOperationSpecification pushSpec = createPushSpec(pushURIs, pushRefSpecs, repository); return new PushOperation(repository, pushSpec, false, DEFAULT_TIMEOUT); } /** * Creates a push operation specification for the given push uris to the * given push operation specification. * * @param pushURIs * the push uri's * @param pushRefSpecs * the push ref specs * @param repository * the repository * @return the push operation specification * @throws CoreException * the core exception */ private static PushOperationSpecification createPushSpec(Collection<URIish> pushURIs, Collection<RefSpec> pushRefSpecs, Repository repository) throws CoreException { try { PushOperationSpecification pushSpec = new PushOperationSpecification(); for (URIish uri : pushURIs) { Collection<RemoteRefUpdate> remoteRefUpdates = Transport.open(repository, uri) .findRemoteRefUpdatesFor(pushRefSpecs); pushSpec.addURIRefUpdates(uri, remoteRefUpdates); } return pushSpec; } catch (NotSupportedException e) { throw new CoreException( createStatus(e, "Could not connect repository \"{0}\" to a remote", repository.toString())); } catch (IOException e) { throw new CoreException( createStatus(e, "Could not convert remote specifications for repository \"{0}\" to a remote", repository.toString())); } } /** * Gets the push uris from the given remoteConfig. * * @param remoteConfig * the remote config * @return the push ur is */ private static Collection<URIish> getPushURIs(RemoteConfig remoteConfig) { List<URIish> pushURIs = new ArrayList<URIish>(); for (URIish uri : remoteConfig.getPushURIs()) { pushURIs.add(uri); } if (pushURIs.isEmpty() && !remoteConfig.getURIs().isEmpty()) { pushURIs.add(remoteConfig.getURIs().get(0)); } return pushURIs; } /** * Gets the push RefSpecs from the given remote configuration. If none is * defined, a default refspec is returned with * {@link #DEFAULT_REFSPEC_SOURCE} and {@link #DEFAULT_REFSPEC_DESTINATION}. * * @param config * the remote config to get the push specs from * @return the push specs to use for the given remote configuration. */ private static List<RefSpec> getPushRefSpecs(RemoteConfig config) { List<RefSpec> pushRefSpecs = new ArrayList<RefSpec>(); List<RefSpec> remoteConfigPushRefSpecs = config.getPushRefSpecs(); if (!remoteConfigPushRefSpecs.isEmpty()) { pushRefSpecs.addAll(remoteConfigPushRefSpecs); } else { // default is to push current HEAD to remote MASTER pushRefSpecs.add( new RefSpec().setSource(DEFAULT_REFSPEC_SOURCE).setDestination(DEFAULT_REFSPEC_DESTINATION)); } return pushRefSpecs; } private static Collection<RefSpec> createForceRefSpecs(boolean forceUpdate, Collection<RefSpec> refSpecs) { List<RefSpec> newRefSpecs = new ArrayList<RefSpec>(); for (RefSpec refSpec : refSpecs) { newRefSpecs.add(refSpec.setForceUpdate(forceUpdate)); } return newRefSpecs; } public static boolean hasFailedEntries(PushOperationResult pushOperationResult) { return !getFailedUpdates(pushOperationResult).isEmpty(); } public static Collection<RemoteRefUpdate> getFailedUpdates(PushOperationResult pushOperationResult) { List<RemoteRefUpdate> allFailedRefUpdates = new ArrayList<RemoteRefUpdate>(); for (URIish uri : pushOperationResult.getURIs()) { allFailedRefUpdates.addAll(getFailedUpdates(uri, pushOperationResult)); } return allFailedRefUpdates; } public static Collection<RemoteRefUpdate> getFailedUpdates(URIish uri, PushOperationResult pushOperationResult) { return getFailedUpdates(pushOperationResult.getPushResult(uri)); } private static Collection<RemoteRefUpdate> getFailedUpdates(PushResult pushResult) { List<RemoteRefUpdate> failedRefUpdates = new ArrayList<RemoteRefUpdate>(); if (pushResult == null || pushResult.getRemoteUpdates() == null) { return failedRefUpdates; } for (RemoteRefUpdate update : pushResult.getRemoteUpdates()) { if (org.eclipse.jgit.transport.RemoteRefUpdate.Status.OK != update.getStatus()) { failedRefUpdates.add(update); } } return failedRefUpdates; } /** * Gets the repository that is configured to the given project. Returns * <code>null</code> if the given project is not git shared. * * @param project * the project * @return the repository */ public static Repository getRepository(IProject project) { Assert.isLegal(project != null, "Could not get repository. No project provided"); RepositoryMapping repositoryMapping = RepositoryMapping.getMapping(project); if (repositoryMapping == null) { return null; } return repositoryMapping.getRepository(); } /** * Gets the repository that is configured to the given project. Throws a * CoreException if the given project is not git shared. * * @param project * the project * @return the repository * * @throws CoreException if the project is not git shared */ public static Repository checkedGetRepository(IProject project) throws CoreException { Repository repository = getRepository(project); if (repository == null) { throw new CoreException(new Status(IStatus.ERROR, EGitCoreActivator.PLUGIN_ID, NLS.bind("No repository found for project {0}. Please ensure it is shared via git.", project.getName()))); } return repository; } /** * Gets the UserConfig from the given repository. The UserConfig of a repo * holds the default author and committer. * * @param repository * the repository * @return the user configuration * @throws CoreException * * @see PersonIdent(Repository) * @see CommittHelper#calculateCommitInfo */ private static UserConfig getUserConfig(Repository repository) throws CoreException { Assert.isLegal(repository != null, "Could not get user configuration. No repository provided."); if (repository.getConfig() == null) { throw new CoreException(createStatus(null, "no user configuration (author, committer) are present in repository \"{0}\"", repository.toString())); } return repository.getConfig().get(UserConfig.KEY); } private static String getFormattedUser(String name, String email) { return new StringBuilder(name).append(" <").append(email).append('>').toString(); } /** * Returns the configuration of the remote repository that is set to the * given repository. * <code>null</null> if none was configured or if there's no remote repo configured. * * @param repository * the repository to get the remote repo configuration from * @return the configurtion of the remote repository * @throws CoreException * the core exception */ private static RemoteConfig getRemoteConfig(Repository repository) throws CoreException { Assert.isLegal(repository != null, "Could not get configuration. No repository provided."); String currentBranch = getCurrentBranch(repository); String remote = getRemoteName(currentBranch, repository); return getRemoteByName(remote, repository); } /** * Returns the remote config for the given remote in the given repository * * @param remote * @param repository * @return * @throws CoreException */ public static RemoteConfig getRemoteByName(String remote, Repository repository) throws CoreException { Assert.isLegal(repository != null, "Could not get configuration. No repository provided."); List<RemoteConfig> allRemotes = getAllRemoteConfigs(repository); return getRemoteConfig(remote, allRemotes); } private static String getCurrentBranch(Repository repository) throws CoreException { String branch = null; try { branch = repository.getBranch(); } catch (IOException e) { throw new CoreException( createStatus(e, "Could not get current branch on repository \"{0}\"", repository.toString())); } return branch; } /** * Gets the remote config with the given name from the list of remote * configs. Returns <code>null</code> if it was not found. * * @param remoteName * the remote name * @param remoteRepositories * the remote repositories * @return the remote config * * @see #getAllRemoteConfigs(Repository) */ public static RemoteConfig getRemoteConfig(String name, List<RemoteConfig> remoteConfigs) { Assert.isLegal(name != null); RemoteConfig remoteConfig = null; for (RemoteConfig config : remoteConfigs) { if (name != null && config.getName().equals(name)) { remoteConfig = config; break; } } return remoteConfig; } /** * Returns all the remote configs from the given repository. * * @param repository * the repository to retrieve the remote configs of * @return the remote configs that are available on the repository * @throws CoreException */ public static List<RemoteConfig> getAllRemoteConfigs(Repository repository) throws CoreException { if (repository == null) { return Collections.emptyList(); } try { return RemoteConfig.getAllRemoteConfigs(repository.getConfig()); } catch (URISyntaxException e) { throw new CoreException(createStatus(e, "Could not get all remote repositories for repository \"{0}\"", repository.toString())); } } /** * Returns the first configured remote in the given repository whose url * matches the given pattern. * * @param pattern * @param repository * @return * @throws CoreException */ public static RemoteConfig getRemoteByUrl(Pattern pattern, Repository repository) throws CoreException { if (repository == null) { return null; } for (RemoteConfig config : getAllRemoteConfigs(repository)) { if (hasRemoteUrl(pattern, config)) { return config; } } return null; } public static boolean hasRemoteUrl(Pattern pattern, Repository repository) throws CoreException { return getRemoteByUrl(pattern, repository) != null; } public static boolean hasRemoteUrl(Pattern pattern, RemoteConfig config) { if (config == null) { return false; } for (URIish uri : config.getURIs()) { Matcher matcher = pattern.matcher(uri.toString()); if (matcher.find()) { return true; } } return false; } /** * Returns <code>true</code> if the given repository has a remote with the * given name, * * @param name * the remote name that we're looking for * @param repository * the repository to look at * @return true if the given repo has a remote with the given name * @throws CoreException */ public static boolean hasRemote(String name, Repository repository) throws CoreException { return getRemoteByName(name, repository) != null; } /** * Returns <code>true</code> if the given repository has a remote config * with the given name and url. * * @param name * the name that the remote config shall match * @param url * the url that the remote config shall match * @param repository * the repository that is searched * @return * @throws CoreException */ public static boolean hasRemote(String name, String url, Repository repository) throws CoreException { RemoteConfig remoteConfig = getRemoteByName(name, repository); if (remoteConfig == null) { return false; } return hasRemoteUrl(Pattern.compile(RegexUtils.toPatternString(url)), remoteConfig); } /** * Returns <code>true</code> if the given repository has several configured * remotes * * @param repository * @return * @throws CoreException * * @see git config file: "[remote..." * @see #getAllRemoteConfigs * @see RemoteConfig#getAllRemoteConfigs * */ public static boolean hasMultipleRemotes(Repository repository) throws CoreException { return getAllRemoteConfigs(repository).size() > 1; } /** * Returns the name of the remote repository for the given branch. If * there's no current branch or no remote configured to it, the default * remote is returned ("origin"). * * @param branch * the branch * @param repository * the repository * @return the remote name */ private static String getRemoteName(String branch, Repository repository) { String remoteName = null; if (ObjectId.isId(branch)) { remoteName = Constants.DEFAULT_REMOTE_NAME; } else { remoteName = repository.getConfig().getString(ConfigConstants.CONFIG_BRANCH_SECTION, branch, ConfigConstants.CONFIG_REMOTE_SECTION); if (remoteName == null) { remoteName = Constants.DEFAULT_REMOTE_NAME; } } return remoteName; } public static void addRemoteTo(String remoteName, String uri, Repository repository) throws MalformedURLException, URISyntaxException, IOException { addRemoteTo(remoteName, new URIish(uri), repository); } /** * Adds the given uri of a remote repository to the given repository by the * given name. * * @param remoteName * the name to use for the remote repository * @param uri * the uri of the remote repository * @param repository * the repository to add the remote to * @throws URISyntaxException * the uRI syntax exception * @throws MalformedURLException * the malformed url exception * @throws IOException * Signals that an I/O exception has occurred. */ public static void addRemoteTo(String remoteName, URIish uri, Repository repository) throws URISyntaxException, MalformedURLException, IOException { StoredConfig config = repository.getConfig(); RemoteConfig remoteConfig = new RemoteConfig(config, remoteName); remoteConfig.addURI(uri); remoteConfig.update(config); config.save(); } private static IStatus createStatus(Exception e, String message, String... arguments) throws CoreException { IStatus status = null; if (e == null) { status = new Status(IStatus.ERROR, EGitCoreActivator.PLUGIN_ID, NLS.bind(message, arguments)); } else { status = new Status(IStatus.ERROR, EGitCoreActivator.PLUGIN_ID, NLS.bind(message, arguments), e); } return status; } /** * Returns <code>true</code> if the given repository has uncommitted * changes. Uncommitted changes taken into account are * <ul> * <li>freshly added (but uncommitted resources)</li> * <li>changed (but uncommitted)</li> * <li>modified (but uncommitted resources)</li> * <li>removed (but uncommitted resources)</li> * </ul> * * @param repository * the repository to check for uncommitted changes * @return * @throws IOException * @throws NoWorkTreeException * @throws GitAPIException */ public static boolean isDirty(Repository repository) throws NoWorkTreeException, IOException, GitAPIException { boolean hasChanges = false; org.eclipse.jgit.api.Status repoStatus = new Git(repository).status().call(); hasChanges |= !repoStatus.getAdded().isEmpty(); hasChanges |= !repoStatus.getChanged().isEmpty(); hasChanges |= !repoStatus.getModified().isEmpty(); hasChanges |= !repoStatus.getRemoved().isEmpty(); hasChanges |= !repoStatus.getConflicting().isEmpty(); hasChanges |= !repoStatus.getMissing().isEmpty(); return hasChanges; } /** * Returns the changes in the index of the HEAD branch in the given * repository. Returns the index diff if there are changes, * <code>null</code> otherwise. * * @param repo * the repository to get index changes for * @param monitor * the monitor to report progress to * @return the changes in the index or null; * @throws IOException */ public static IndexDiff getIndexChanges(Repository repo, IProgressMonitor monitor) throws IOException { EclipseGitProgressTransformer jgitMonitor = new EclipseGitProgressTransformer(monitor); IndexDiff indexDiff = new IndexDiff(repo, Constants.HEAD, IteratorService.createInitialIterator(repo)); if (!indexDiff.diff(jgitMonitor, 0, 0, NLS.bind("Repository: {0}", repo.getDirectory().getPath()))) { return null; } return indexDiff; } /** * Returns <code>true</code> if the given branch in the given repository has * local commits that were not pushed to its remote yet. * * @param repository * @param branchName * @return * @throws IOException */ public static boolean hasCommitsToBePushed(Repository repository) throws IOException { BranchTrackingStatus status = BranchTrackingStatus.of(repository, Constants.MASTER); if (status == null) { return false; } return status.getAheadCount() > 0; } /** * Returns the push uri for the given remote config. The logic implemented * is taken from RepositoriesViewContentProvider#getChildren - case REMOTE: * * <pre> * <code> * if (!rc.getPushURIs().isEmpty()) * firstUri = rc.getPushURIs().get(0); * else * firstUri = rc.getURIs().get(0); * </code> * </pre> * * * * @param config * @return */ public static URIish getPushURI(RemoteConfig config) { if (config.getPushURIs().isEmpty()) { return config.getURIs().get(0); } else { return config.getPushURIs().get(0); } } /** * Returns the fetch uri for the given remote config. The logic implemented * is taken from RepositoriesViewContentProvider#getChildren - case REMOTE: * * <pre> * <code> * if (!rc.getURIs().isEmpty()) * children.add(new FetchNode(node, node.getRepository(), rc * .getURIs().get(0).toPrivateString())); * </code> * </pre> * * * * @param config * @return */ public static URIish getFetchURI(RemoteConfig config) { if (!config.getURIs().isEmpty()) { return config.getURIs().get(0); } else { return null; } } /** * Fetches according to the fetch specs in the given remote config to the * given repo. If the given remote config has no fetch spec, then * +refs/heads/*:refs/remotes/<remote-name>/* is used * * @param config * the remote config to use when fetching * @param repo * the repo to fetch to * @param monitor * the monitor to report progress to * @return * @throws InvocationTargetException */ public static FetchResult fetch(RemoteConfig config, Repository repo, IProgressMonitor monitor) throws InvocationTargetException { FetchOperation op = null; if (!config.getFetchRefSpecs().isEmpty()) { op = new FetchOperation(repo, config, DEFAULT_TIMEOUT, false); } else { List<RefSpec> refSpecs = Arrays .asList(new RefSpec("+refs/heads/*:refs/remotes/" + config.getName() + "/*")); URIish fetchURI = getFetchURI(config); op = new FetchOperation(repo, fetchURI, refSpecs, DEFAULT_TIMEOUT, false); } op.run(monitor); return op.getOperationResult(); } /** * Returns <code>true</code> if the given repo has commits that are not * contained withing the repo attached to it via the given remote. It is * ahead of the given remote config. * This will work for non{@link BranchTrackingStatus#of(Repository, String)} will tell you if the * given branch is ahead of it's tracking branch. It only works with a * branch that is tracking another branch. * * @param repo * the repo to check * @param remote * the name of the remote to check against * @param monitor * the monitor to report progress to * @return * @throws IOException * @throws InvocationTargetException * @throws URISyntaxException * * @see BranchTrackingStatus#of */ public static boolean isAhead(Repository repo, String remote, IProgressMonitor monitor) throws IOException, InvocationTargetException, URISyntaxException { Assert.isLegal(remote != null); Assert.isLegal(repo != null); if (remote.equals(getRemote(repo.getBranch(), repo.getConfig()))) { BranchTrackingStatus status = BranchTrackingStatus.of(repo, repo.getBranch()); if (status != null) { return status.getAheadCount() > 0; } } return isNonTrackingBranchAhead(repo, remote, monitor); } /** * Returns the remote for a given branch and config. Returns * <code>null</code> if none is explicitly configured. * * <pre> * [branch "master"] * remote = origin * </pre> * * @param branch * the branch to get the configured remote for the * @param config * the configuration to look into * @return the configured remote or null. */ public static String getRemote(String branch, Config config) { return config.getString(ConfigConstants.CONFIG_BRANCH_SECTION, branch, ConfigConstants.CONFIG_KEY_REMOTE); } private static boolean isNonTrackingBranchAhead(Repository repo, String remote, IProgressMonitor monitor) throws URISyntaxException, InvocationTargetException, IOException { RemoteConfig remoteConfig = new RemoteConfig(repo.getConfig(), remote); FetchResult fetchResult = fetch(remoteConfig, repo, monitor); Ref ref = fetchResult.getAdvertisedRef(Constants.HEAD); if (ref == null) { return false; } Ref currentBranchRef = repo.getRef(repo.getBranch()); RevWalk walk = new RevWalk(repo); RevCommit localCommit = walk.parseCommit(currentBranchRef.getObjectId()); RevCommit trackingCommit = walk.parseCommit(ref.getObjectId()); walk.setRevFilter(RevFilter.MERGE_BASE); walk.markStart(localCommit); walk.markStart(trackingCommit); RevCommit mergeBase = walk.next(); walk.reset(); walk.setRevFilter(RevFilter.ALL); int aheadCount = RevWalkUtils.count(walk, localCommit, mergeBase); return aheadCount > 0; } }