/src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java
Java | 1359 lines | 1177 code | 81 blank | 101 comment | 113 complexity | 9d95da254f073b26762a0244162d7369 MD5 | raw file
- package org.jenkinsci.plugins.gitclient;
- import static org.apache.commons.lang.StringUtils.isBlank;
- import static org.apache.commons.lang.StringUtils.removeStart;
- import static org.eclipse.jgit.api.ResetCommand.ResetType.HARD;
- import static org.eclipse.jgit.api.ResetCommand.ResetType.MIXED;
- import static org.eclipse.jgit.lib.Constants.CHARSET;
- import static org.eclipse.jgit.lib.Constants.HEAD;
- import static org.eclipse.jgit.lib.Constants.R_HEADS;
- import static org.eclipse.jgit.lib.Constants.R_REMOTES;
- import static org.eclipse.jgit.lib.Constants.R_TAGS;
- import static org.eclipse.jgit.lib.Constants.typeString;
- import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.OK;
- import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.UP_TO_DATE;
- import hudson.FilePath;
- import hudson.Util;
- import hudson.model.TaskListener;
- import hudson.plugins.git.Branch;
- import hudson.plugins.git.GitException;
- import hudson.plugins.git.GitLockFailedException;
- import hudson.plugins.git.IndexEntry;
- import hudson.plugins.git.Revision;
- import hudson.util.IOUtils;
- import java.io.File;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.PrintWriter;
- import java.io.StringWriter;
- import java.io.Writer;
- import java.net.URISyntaxException;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.Comparator;
- import java.util.Date;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.List;
- import java.util.ListIterator;
- import java.util.Map;
- import java.util.Set;
- import java.util.regex.Pattern;
- import javax.annotation.Nullable;
- import org.apache.commons.lang.time.FastDateFormat;
- import org.eclipse.jgit.api.AddNoteCommand;
- import org.eclipse.jgit.api.CommitCommand;
- import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode;
- import org.eclipse.jgit.api.FetchCommand;
- import org.eclipse.jgit.api.Git;
- import org.eclipse.jgit.api.ListBranchCommand;
- import org.eclipse.jgit.api.LsRemoteCommand;
- import org.eclipse.jgit.api.MergeResult;
- import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
- import org.eclipse.jgit.api.ResetCommand;
- import org.eclipse.jgit.api.ShowNoteCommand;
- import org.eclipse.jgit.api.errors.CheckoutConflictException;
- import org.eclipse.jgit.api.errors.GitAPIException;
- import org.eclipse.jgit.api.errors.JGitInternalException;
- import org.eclipse.jgit.diff.DiffEntry;
- import org.eclipse.jgit.diff.DiffEntry.ChangeType;
- import org.eclipse.jgit.diff.RenameDetector;
- import org.eclipse.jgit.errors.InvalidPatternException;
- import org.eclipse.jgit.errors.LockFailedException;
- import org.eclipse.jgit.errors.NotSupportedException;
- import org.eclipse.jgit.errors.TransportException;
- import org.eclipse.jgit.fnmatch.FileNameMatcher;
- import org.eclipse.jgit.internal.storage.file.FileRepository;
- import org.eclipse.jgit.lib.Config;
- import org.eclipse.jgit.lib.Constants;
- import org.eclipse.jgit.lib.ObjectId;
- import org.eclipse.jgit.lib.ObjectLoader;
- import org.eclipse.jgit.lib.ObjectReader;
- import org.eclipse.jgit.lib.PersonIdent;
- import org.eclipse.jgit.lib.Ref;
- import org.eclipse.jgit.lib.RefDatabase;
- import org.eclipse.jgit.lib.RefUpdate;
- import org.eclipse.jgit.lib.RefUpdate.Result;
- import org.eclipse.jgit.lib.Repository;
- import org.eclipse.jgit.lib.RepositoryBuilder;
- import org.eclipse.jgit.lib.StoredConfig;
- import org.eclipse.jgit.merge.MergeStrategy;
- import org.eclipse.jgit.notes.Note;
- import org.eclipse.jgit.revwalk.RevCommit;
- import org.eclipse.jgit.revwalk.RevFlag;
- import org.eclipse.jgit.revwalk.RevFlagSet;
- import org.eclipse.jgit.revwalk.RevObject;
- import org.eclipse.jgit.revwalk.RevSort;
- import org.eclipse.jgit.revwalk.RevTree;
- import org.eclipse.jgit.revwalk.RevWalk;
- import org.eclipse.jgit.revwalk.filter.MaxCountRevFilter;
- import org.eclipse.jgit.revwalk.filter.RevFilter;
- import org.eclipse.jgit.submodule.SubmoduleWalk;
- import org.eclipse.jgit.transport.CredentialsProvider;
- import org.eclipse.jgit.transport.FetchConnection;
- import org.eclipse.jgit.transport.RefSpec;
- import org.eclipse.jgit.transport.RemoteConfig;
- import org.eclipse.jgit.transport.SshSessionFactory;
- import org.eclipse.jgit.transport.TagOpt;
- import org.eclipse.jgit.transport.Transport;
- import org.eclipse.jgit.transport.URIish;
- import org.eclipse.jgit.transport.PushResult;
- import org.eclipse.jgit.transport.RemoteRefUpdate;
- import org.eclipse.jgit.treewalk.TreeWalk;
- import org.eclipse.jgit.treewalk.filter.TreeFilter;
- import org.jenkinsci.plugins.gitclient.trilead.SmartCredentialsProvider;
- import org.jenkinsci.plugins.gitclient.trilead.TrileadSessionFactory;
- import com.cloudbees.plugins.credentials.common.StandardCredentials;
- import com.google.common.base.Functions;
- import com.google.common.base.Predicate;
- import com.google.common.base.Predicates;
- import com.google.common.collect.Iterables;
- import com.google.common.collect.Lists;
- import edu.umd.cs.findbugs.annotations.NonNull;
- import org.eclipse.jgit.api.RebaseCommand.Operation;
- import org.eclipse.jgit.api.RebaseResult;
- /**
- * GitClient pure Java implementation using JGit.
- * Goal is to eventually get a full java implementation for GitClient
- * <b>
- * For internal use only, don't use directly. See {@link org.jenkinsci.plugins.gitclient.Git}
- * </b>
- *
- * @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a>
- * @author Kohsuke Kawaguchi
- */
- public class JGitAPIImpl extends LegacyCompatibleGitAPIImpl {
- private static final long serialVersionUID = 1L;
- private final TaskListener listener;
- private PersonIdent author, committer;
- private transient CredentialsProvider provider;
- JGitAPIImpl(File workspace, TaskListener listener) {
- /* If workspace is null, then default to current directory to match
- * CliGitAPIImpl behavior */
- super(workspace == null ? new File(".") : workspace);
- this.listener = listener;
- // to avoid rogue plugins from clobbering what we use, always
- // make a point of overwriting it with ours.
- SshSessionFactory.setInstance(new TrileadSessionFactory());
- }
- /**
- * clearCredentials.
- */
- public void clearCredentials() {
- asSmartCredentialsProvider().clearCredentials();
- }
- /** {@inheritDoc} */
- public void addCredentials(String url, StandardCredentials credentials) {
- asSmartCredentialsProvider().addCredentials(url, credentials);
- }
- /** {@inheritDoc} */
- public void addDefaultCredentials(StandardCredentials credentials) {
- asSmartCredentialsProvider().addDefaultCredentials(credentials);
- }
- private synchronized SmartCredentialsProvider asSmartCredentialsProvider() {
- if (!(provider instanceof SmartCredentialsProvider)) {
- provider = new SmartCredentialsProvider(listener);
- }
- return ((SmartCredentialsProvider) provider);
- }
- /**
- * setCredentialsProvider.
- *
- * @param prov a {@link org.eclipse.jgit.transport.CredentialsProvider} object.
- */
- public synchronized void setCredentialsProvider(CredentialsProvider prov) {
- this.provider = prov;
- }
- private synchronized CredentialsProvider getProvider() {
- return provider;
- }
- /** {@inheritDoc} */
- public GitClient subGit(String subdir) {
- return new JGitAPIImpl(new File(workspace, subdir), listener);
- }
- /** {@inheritDoc} */
- public void setAuthor(String name, String email) throws GitException {
- author = new PersonIdent(name,email);
- }
- /** {@inheritDoc} */
- public void setCommitter(String name, String email) throws GitException {
- committer = new PersonIdent(name,email);
- }
- /**
- * init.
- *
- * @throws hudson.plugins.git.GitException if underlying git operation fails.
- * @throws java.lang.InterruptedException if interrupted.
- */
- public void init() throws GitException, InterruptedException {
- init_().workspace(workspace.getAbsolutePath()).execute();
- }
- private void doInit(String workspace, boolean bare) throws GitException {
- try {
- Git.init().setBare(bare).setDirectory(new File(workspace)).call();
- } catch (GitAPIException e) {
- throw new GitException(e);
- }
- }
- /**
- * checkout.
- *
- * @return a {@link org.jenkinsci.plugins.gitclient.CheckoutCommand} object.
- */
- public CheckoutCommand checkout() {
- return new CheckoutCommand() {
- public String ref;
- public String branch;
- public boolean deleteBranch;
- public List<String> sparseCheckoutPaths = Collections.emptyList();
- public CheckoutCommand ref(String ref) {
- this.ref = ref;
- return this;
- }
- public CheckoutCommand branch(String branch) {
- this.branch = branch;
- return this;
- }
- public CheckoutCommand deleteBranchIfExist(boolean deleteBranch) {
- this.deleteBranch = deleteBranch;
- return this;
- }
- public CheckoutCommand sparseCheckoutPaths(List<String> sparseCheckoutPaths) {
- this.sparseCheckoutPaths = sparseCheckoutPaths == null ? Collections.<String>emptyList() : sparseCheckoutPaths;
- return this;
- }
- public CheckoutCommand timeout(Integer timeout) {
- // noop in jgit
- return this;
- }
- public void execute() throws GitException, InterruptedException {
- if(! sparseCheckoutPaths.isEmpty()) {
- listener.getLogger().println("[ERROR] JGit doesn't support sparse checkout.");
- throw new UnsupportedOperationException("not implemented yet");
- }
- if (branch == null)
- doCheckout(ref);
- else if (deleteBranch)
- doCheckoutCleanBranch(branch, ref);
- else
- doCheckout(ref, branch);
- }
- };
- }
- private void doCheckout(String ref) throws GitException {
- boolean retried = false;
- Repository repo = null;
- while (true) {
- try {
- repo = getRepository();
- try {
- // force in Jgit is "-B" in Git CLI, meaning no forced switch,
- // but forces recreation of the branch.
- // we need to take back all open changes to get the equivalent
- // of git checkout -f
- git(repo).reset().setMode(HARD).call();
- } catch (GitAPIException e) {
- throw new GitException("Could not reset the workspace before checkout of " + ref, e);
- } catch (JGitInternalException e) {
- if (e.getCause() instanceof LockFailedException){
- throw new GitLockFailedException("Could not lock repository. Please try again", e);
- } else {
- throw e;
- }
- }
- if (repo.resolve(ref) != null) {
- // ref is either an existing reference or a shortcut to a tag or branch (without refs/heads/)
- git(repo).checkout().setName(ref).setForce(true).call();
- return;
- }
- List<String> remoteTrackingBranches = new ArrayList<String>();
- for (String remote : repo.getRemoteNames()) {
- // look for exactly ONE remote tracking branch
- String matchingRemoteBranch = Constants.R_REMOTES + remote + "/" + ref;
- if (repo.getRef(matchingRemoteBranch) != null) {
- remoteTrackingBranches.add(matchingRemoteBranch);
- }
- }
- if (remoteTrackingBranches.isEmpty()) {
- throw new GitException("No matching revision for " + ref + " found.");
- }
- if (remoteTrackingBranches.size() > 1) {
- throw new GitException("Found more than one matching remote tracking branches for " + ref + " : " + remoteTrackingBranches);
- }
- String matchingRemoteBranch = remoteTrackingBranches.get(0);
- listener.getLogger().format("[WARNING] Automatically creating a local branch '%s' tracking remote branch '%s'", ref, removeStart(matchingRemoteBranch, Constants.R_REMOTES));
- git(repo).checkout()
- .setCreateBranch(true)
- .setName(ref)
- .setUpstreamMode(SetupUpstreamMode.SET_UPSTREAM)
- .setStartPoint(matchingRemoteBranch).call();
- return;
- } catch (CheckoutConflictException e) {
- if (repo != null) {
- repo.close(); /* Close and null for immediate reuse */
- repo = null;
- }
- // "git checkout -f" seems to overwrite local untracked files but git CheckoutCommand doesn't.
- // see the test case GitAPITestCase.test_localCheckoutConflict. so in this case we manually
- // clean up the conflicts and try it again
- if (retried)
- throw new GitException("Could not checkout " + ref, e);
- retried = true;
- repo = getRepository(); /* Reusing repo declared and assigned earlier */
- for (String path : e.getConflictingPaths()) {
- File conflict = new File(repo.getWorkTree(), path);
- if (!conflict.delete() && conflict.exists()) {
- listener.getLogger().println("[WARNING] conflicting path " + conflict + " not deleted");
- }
- }
- } catch (IOException e) {
- throw new GitException("Could not checkout " + ref, e);
- } catch (GitAPIException e) {
- throw new GitException("Could not checkout " + ref, e);
- } catch (JGitInternalException e) {
- if (Pattern.matches("Cannot lock.+", e.getMessage())){
- throw new GitLockFailedException("Could not lock repository. Please try again", e);
- } else {
- throw e;
- }
- } finally {
- if (repo != null) repo.close();
- }
- }
- }
- private void doCheckout(String ref, String branch) throws GitException {
- Repository repo = null;
- try {
- repo = getRepository();
- if (ref == null) ref = repo.resolve(HEAD).name();
- git(repo).checkout().setName(branch).setCreateBranch(true).setForce(true).setStartPoint(ref).call();
- } catch (IOException e) {
- throw new GitException("Could not checkout " + branch + " with start point " + ref, e);
- } catch (GitAPIException e) {
- throw new GitException("Could not checkout " + branch + " with start point " + ref, e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- private void doCheckoutCleanBranch(String branch, String ref) throws GitException {
- Repository repo = null;
- try {
- repo = getRepository();
- RefUpdate refUpdate = repo.updateRef(R_HEADS + branch);
- refUpdate.setNewObjectId(repo.resolve(ref));
- switch (refUpdate.forceUpdate()) {
- case NOT_ATTEMPTED:
- case LOCK_FAILURE:
- case REJECTED:
- case REJECTED_CURRENT_BRANCH:
- case IO_FAILURE:
- case RENAMED:
- throw new GitException("Could not update " + branch + " to " + ref);
- }
- doCheckout(branch);
- } catch (IOException e) {
- throw new GitException("Could not checkout " + branch + " with start point " + ref, e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- /** {@inheritDoc} */
- public void add(String filePattern) throws GitException {
- Repository repo = null;
- try {
- repo = getRepository();
- git(repo).add().addFilepattern(filePattern).call();
- } catch (GitAPIException e) {
- throw new GitException(e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- private Git git(Repository repo) {
- return Git.wrap(repo);
- }
- /** {@inheritDoc} */
- public void commit(String message) throws GitException {
- Repository repo = null;
- try {
- repo = getRepository();
- CommitCommand cmd = git(repo).commit().setMessage(message);
- if (author!=null)
- cmd.setAuthor(author);
- if (committer!=null)
- cmd.setCommitter(new PersonIdent(committer,new Date()));
- cmd.call();
- } catch (GitAPIException e) {
- throw new GitException(e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- /** {@inheritDoc} */
- public void branch(String name) throws GitException {
- Repository repo = null;
- try {
- repo = getRepository();
- git(repo).branchCreate().setName(name).call();
- } catch (GitAPIException e) {
- throw new GitException(e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- /** {@inheritDoc} */
- public void deleteBranch(String name) throws GitException {
- Repository repo = null;
- try {
- repo = getRepository();
- git(repo).branchDelete().setForce(true).setBranchNames(name).call();
- } catch (GitAPIException e) {
- throw new GitException(e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- /**
- * getBranches.
- *
- * @return a {@link java.util.Set} object.
- * @throws hudson.plugins.git.GitException if underlying git operation fails.
- */
- public Set<Branch> getBranches() throws GitException {
- Repository repo = null;
- try {
- repo = getRepository();
- List<Ref> refs = git(repo).branchList().setListMode(ListBranchCommand.ListMode.ALL).call();
- Set<Branch> branches = new HashSet<Branch>(refs.size());
- for (Ref ref : refs) {
- branches.add(new Branch(ref));
- }
- return branches;
- } catch (GitAPIException e) {
- throw new GitException(e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- /**
- * getRemoteBranches.
- *
- * @return a {@link java.util.Set} object.
- * @throws hudson.plugins.git.GitException if underlying git operation fails.
- */
- public Set<Branch> getRemoteBranches() throws GitException {
- Repository repo = null;
- try {
- repo = getRepository();
- List<Ref> refs = git(repo).branchList().setListMode(ListBranchCommand.ListMode.REMOTE).call();
- Set<Branch> branches = new HashSet<Branch>(refs.size());
- for (Ref ref : refs) {
- branches.add(new Branch(ref));
- }
- return branches;
- } catch (GitAPIException e) {
- throw new GitException(e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- /** {@inheritDoc} */
- public void tag(String name, String message) throws GitException {
- Repository repo = null;
- try {
- repo = getRepository();
- git(repo).tag().setName(name).setMessage(message).setForceUpdate(true).call();
- } catch (GitAPIException e) {
- throw new GitException(e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- /** {@inheritDoc} */
- public boolean tagExists(String tagName) throws GitException {
- Repository repo = null;
- try {
- repo = getRepository();
- Ref tag = repo.getRefDatabase().getRef(R_TAGS + tagName);
- return tag != null;
- } catch (IOException e) {
- throw new GitException(e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- /**
- * fetch_.
- *
- * @return a {@link org.jenkinsci.plugins.gitclient.FetchCommand} object.
- */
- public org.jenkinsci.plugins.gitclient.FetchCommand fetch_() {
- return new org.jenkinsci.plugins.gitclient.FetchCommand() {
- public URIish url;
- public List<RefSpec> refspecs;
- // JGit 3.3.0 thru 3.6.0 prune more branches than expected
- // Refer to GitAPITestCase.test_fetch_with_prune()
- private boolean shouldPrune = false;
- public boolean tags = true;
- public org.jenkinsci.plugins.gitclient.FetchCommand from(URIish remote, List<RefSpec> refspecs) {
- this.url = remote;
- this.refspecs = refspecs;
- return this;
- }
- public org.jenkinsci.plugins.gitclient.FetchCommand prune() {
- //throw new UnsupportedOperationException("JGit don't (yet) support pruning during fetch");
- shouldPrune = true;
- return this;
- }
- public org.jenkinsci.plugins.gitclient.FetchCommand shallow(boolean shallow) {
- if (shallow) {
- listener.getLogger().println("[WARNING] JGit doesn't support shallow clone. This flag is ignored");
- }
- return this;
- }
- public org.jenkinsci.plugins.gitclient.FetchCommand timeout(Integer timeout) {
- // noop in jgit
- return this;
- }
- public org.jenkinsci.plugins.gitclient.FetchCommand tags(boolean tags) {
- this.tags = tags;
- return this;
- }
- public org.jenkinsci.plugins.gitclient.FetchCommand depth(Integer depth) {
- listener.getLogger().println("[WARNING] JGit doesn't support shallow clone and therefore depth is meaningless. This flag is ignored");
- return this;
- }
- public void execute() throws GitException, InterruptedException {
- Repository repo = null;
- FetchCommand fetch = null;
- try {
- repo = getRepository();
- Git git = git(repo);
- List<RefSpec> refSpecs = new ArrayList<RefSpec>();
- if (tags) {
- // see http://stackoverflow.com/questions/14876321/jgit-fetch-dont-update-tag
- refSpecs.add(new RefSpec("+refs/tags/*:refs/tags/*"));
- }
- if (refspecs != null)
- for (RefSpec rs: refspecs)
- if (rs != null)
- refSpecs.add(rs);
- if (shouldPrune) {
- // since prune is broken in JGit, we go the trivial way:
- // delete all refs matching the right side of the refspecs
- // then fetch and let the git recreate them.
- List<Ref> refs = git.branchList().setListMode(ListBranchCommand.ListMode.REMOTE).call();
- List<String> toDelete = new ArrayList<String>(refs.size());
- for (ListIterator<Ref> it = refs.listIterator(); it.hasNext(); ) {
- Ref branchRef = it.next();
- for (RefSpec rs : refSpecs) {
- if (rs.matchDestination(branchRef)) {
- toDelete.add(branchRef.getName());
- break;
- }
- }
- }
- if (!toDelete.isEmpty()) {
- // we need force = true because usually not all remote branches will be merged into the current branch.
- git.branchDelete().setForce(true).setBranchNames(toDelete.toArray(new String[toDelete.size()])).call();
- }
- }
- fetch = git.fetch();
- fetch.setTagOpt(tags ? TagOpt.FETCH_TAGS : TagOpt.NO_TAGS);
- fetch.setRemote(url.toString());
- fetch.setCredentialsProvider(getProvider());
- fetch.setRefSpecs(refSpecs);
- // fetch.setRemoveDeletedRefs(shouldPrune);
- fetch.call();
- } catch (GitAPIException e) {
- throw new GitException(e);
- } finally {
- if (fetch != null && fetch.getRepository() != null) fetch.getRepository().close();
- if (repo != null) repo.close();
- }
- }
- };
- }
- /**
- * {@inheritDoc}
- *
- * @param url a {@link org.eclipse.jgit.transport.URIish} object.
- * @param refspecs a {@link java.util.List} object.
- * @throws hudson.plugins.git.GitException if any.
- * @throws java.lang.InterruptedException if any.
- */
- public void fetch(URIish url, List<RefSpec> refspecs) throws GitException, InterruptedException {
- fetch_().from(url, refspecs).execute();
- }
- /** {@inheritDoc} */
- public void fetch(String remoteName, RefSpec... refspec) throws GitException {
- Repository repo = null;
- try {
- repo = getRepository();
- FetchCommand fetch = git(repo).fetch().setTagOpt(TagOpt.FETCH_TAGS);
- if (remoteName != null) fetch.setRemote(remoteName);
- fetch.setCredentialsProvider(getProvider());
- // see http://stackoverflow.com/questions/14876321/jgit-fetch-dont-update-tag
- List<RefSpec> refSpecs = new ArrayList<RefSpec>();
- refSpecs.add(new RefSpec("+refs/tags/*:refs/tags/*"));
- if (refspec != null && refspec.length > 0)
- for (RefSpec rs: refspec)
- if (rs != null)
- refSpecs.add(rs);
- fetch.setRefSpecs(refSpecs);
- fetch.call();
- } catch (GitAPIException e) {
- throw new GitException(e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- /** {@inheritDoc} */
- public void fetch(String remoteName, RefSpec refspec) throws GitException {
- fetch(remoteName, new RefSpec[] {refspec});
- }
- /** {@inheritDoc} */
- public void ref(String refName) throws GitException, InterruptedException {
- refName = refName.replace(' ', '_');
- Repository repo = null;
- try {
- repo = getRepository();
- RefUpdate refUpdate = repo.updateRef(refName);
- refUpdate.setNewObjectId(repo.getRef(Constants.HEAD).getObjectId());
- switch (refUpdate.forceUpdate()) {
- case NOT_ATTEMPTED:
- case LOCK_FAILURE:
- case REJECTED:
- case REJECTED_CURRENT_BRANCH:
- case IO_FAILURE:
- case RENAMED:
- throw new GitException("Could not update " + refName + " to HEAD");
- }
- } catch (IOException e) {
- throw new GitException("Could not update " + refName + " to HEAD", e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- /** {@inheritDoc} */
- public boolean refExists(String refName) throws GitException, InterruptedException {
- refName = refName.replace(' ', '_');
- Repository repo = null;
- try {
- repo = getRepository();
- Ref ref = repo.getRefDatabase().getRef(refName);
- return ref != null;
- } catch (IOException e) {
- throw new GitException("Error checking ref " + refName, e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- /** {@inheritDoc} */
- public void deleteRef(String refName) throws GitException, InterruptedException {
- refName = refName.replace(' ', '_');
- Repository repo = null;
- try {
- repo = getRepository();
- RefUpdate refUpdate = repo.updateRef(refName);
- // Required, even though this is a forced delete.
- refUpdate.setNewObjectId(repo.getRef(Constants.HEAD).getObjectId());
- refUpdate.setForceUpdate(true);
- switch (refUpdate.delete()) {
- case NOT_ATTEMPTED:
- case LOCK_FAILURE:
- case REJECTED:
- case REJECTED_CURRENT_BRANCH:
- case IO_FAILURE:
- case RENAMED:
- throw new GitException("Could not delete " + refName);
- }
- } catch (IOException e) {
- throw new GitException("Could not delete " + refName, e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- /** {@inheritDoc} */
- public Set<String> getRefNames(String refPrefix) throws GitException, InterruptedException {
- if (refPrefix.isEmpty()) {
- refPrefix = RefDatabase.ALL;
- } else {
- refPrefix = refPrefix.replace(' ', '_');
- }
- Repository repo = null;
- try {
- repo = getRepository();
- Map<String, Ref> refList = repo.getRefDatabase().getRefs(refPrefix);
- // The key set for refList will have refPrefix removed, so to recover it we just grab the full name.
- Set<String> refs = new HashSet<String>(refList.size());
- for (Ref ref : refList.values()) {
- refs.add(ref.getName());
- }
- return refs;
- } catch (IOException e) {
- throw new GitException("Error retrieving refs with prefix " + refPrefix, e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- /** {@inheritDoc} */
- public Map<String, ObjectId> getHeadRev(String url) throws GitException, InterruptedException {
- Map<String, ObjectId> heads = new HashMap<String, ObjectId>();
- try {
- Repository repo = openDummyRepository();
- final Transport tn = Transport.open(repo, new URIish(url));
- tn.setCredentialsProvider(getProvider());
- final FetchConnection c = tn.openFetch();
- try {
- for (final Ref r : c.getRefs()) {
- heads.put(r.getName(), r.getPeeledObjectId() != null ? r.getPeeledObjectId() : r.getObjectId());
- }
- } finally {
- c.close();
- tn.close();
- repo.close();
- }
- } catch (IOException e) {
- throw new GitException(e);
- } catch (URISyntaxException e) {
- throw new GitException(e);
- }
- return heads;
- }
- /** {@inheritDoc} */
- public Map<String, ObjectId> getRemoteReferences(String url, String pattern, boolean headsOnly, boolean tagsOnly)
- throws GitException, InterruptedException {
- Map<String, ObjectId> references = new HashMap<String, ObjectId>();
- String regexPattern = null;
- if (pattern != null) {
- regexPattern = createRefRegexFromGlob(pattern);
- }
- try {
- Repository repo = openDummyRepository();
- LsRemoteCommand lsRemote = new LsRemoteCommand(repo);
- if (headsOnly) {
- lsRemote.setHeads(headsOnly);
- }
- if (tagsOnly) {
- lsRemote.setTags(tagsOnly);
- }
- lsRemote.setRemote(url);
- lsRemote.setCredentialsProvider(getProvider());
- Collection<Ref> refs = lsRemote.call();
- try {
- for (final Ref r : refs) {
- final String refName = r.getName();
- final ObjectId refObjectId =
- r.getPeeledObjectId() != null ? r.getPeeledObjectId() : r.getObjectId();
- if (regexPattern != null) {
- if (refName.matches(regexPattern)) {
- references.put(refName, refObjectId);
- }
- } else {
- references.put(refName, refObjectId);
- }
- }
- } finally {
- repo.close();
- }
- } catch (GitAPIException e) {
- throw new GitException(e);
- } catch (IOException e) {
- throw new GitException(e);
- }
- return references;
- }
- /* Adapted from http://stackoverflow.com/questions/1247772/is-there-an-equivalent-of-java-util-regex-for-glob-type-patterns */
- private String createRefRegexFromGlob(String glob)
- {
- StringBuilder out = new StringBuilder();
- if(glob.startsWith("refs/")) {
- out.append("^");
- } else {
- out.append("^.*/");
- }
- for (int i = 0; i < glob.length(); ++i) {
- final char c = glob.charAt(i);
- switch(c) {
- case '*':
- out.append(".*");
- break;
- case '?':
- out.append('.');
- break;
- case '.':
- out.append("\\.");
- break;
- case '\\':
- out.append("\\\\");
- break;
- default:
- out.append(c);
- break;
- }
- }
- out.append('$');
- return out.toString();
- }
- /** {@inheritDoc} */
- public ObjectId getHeadRev(String remoteRepoUrl, String branchSpec) throws GitException {
- try {
- final String branchName = extractBranchNameFromBranchSpec(branchSpec);
- String regexBranch = createRefRegexFromGlob(branchName);
- Repository repo = openDummyRepository();
- final Transport tn = Transport.open(repo, new URIish(remoteRepoUrl));
- tn.setCredentialsProvider(getProvider());
- final FetchConnection c = tn.openFetch();
- try {
- for (final Ref r : c.getRefs()) {
- if (r.getName().matches(regexBranch)) {
- return r.getPeeledObjectId() != null ? r.getPeeledObjectId() : r.getObjectId();
- }
- }
- } finally {
- c.close();
- tn.close();
- repo.close();
- }
- } catch (IOException e) {
- throw new GitException(e);
- } catch (URISyntaxException e) {
- throw new GitException(e);
- } catch (IllegalStateException e) {
- // "Cannot open session, connection is not authenticated." from com.trilead.ssh2.Connection.openSession
- throw new GitException(e);
- }
- return null;
- }
- /**
- * Creates a empty dummy {@link Repository} to keep JGit happy where it wants a valid {@link Repository} operation
- * for remote objects.
- */
- private Repository openDummyRepository() throws IOException {
- final File tempDir = Util.createTempDir();
- return new FileRepository(tempDir) {
- @Override
- public void close() {
- super.close();
- try {
- Util.deleteRecursive(tempDir);
- } catch (IOException e) {
- // ignore
- }
- }
- };
- }
- /** {@inheritDoc} */
- public String getRemoteUrl(String name) throws GitException {
- final Repository repo = getRepository();
- final String url = repo.getConfig().getString("remote",name,"url");
- repo.close();
- return url;
- }
- /**
- * getRepository.
- *
- * @return a {@link org.eclipse.jgit.lib.Repository} object.
- * @throws hudson.plugins.git.GitException if underlying git operation fails.
- */
- @NonNull
- public Repository getRepository() throws GitException {
- try {
- return new RepositoryBuilder().setWorkTree(workspace).build();
- } catch (IOException e) {
- throw new GitException(e);
- }
- }
- /**
- * getWorkTree.
- *
- * @return a {@link hudson.FilePath} object.
- */
- public FilePath getWorkTree() {
- return new FilePath(workspace);
- }
- /** {@inheritDoc} */
- public void setRemoteUrl(String name, String url) throws GitException {
- Repository repo = null;
- try {
- repo = getRepository();
- StoredConfig config = repo.getConfig();
- config.setString("remote", name, "url", url);
- config.save();
- } catch (IOException e) {
- throw new GitException(e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- /** {@inheritDoc} */
- public void addRemoteUrl(String name, String url) throws GitException, InterruptedException {
- Repository repo = null;
- try {
- repo = getRepository();
- StoredConfig config = repo.getConfig();
- List<String> urls = new ArrayList<String>();
- urls.addAll(Arrays.asList(config.getStringList("remote", name, "url")));
- urls.add(url);
- config.setStringList("remote", name, "url", urls);
- config.save();
- } catch (IOException e) {
- throw new GitException(e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- /** {@inheritDoc} */
- public void addNote(String note, String namespace) throws GitException {
- Repository repo = null;
- RevWalk walk = null;
- ObjectReader or = null;
- try {
- repo = getRepository();
- ObjectId head = repo.resolve(HEAD); // commit to put a note on
- AddNoteCommand cmd = git(repo).notesAdd();
- cmd.setMessage(normalizeNote(note));
- cmd.setNotesRef(qualifyNotesNamespace(namespace));
- or = repo.newObjectReader();
- walk = new RevWalk(or);
- cmd.setObjectId(walk.parseAny(head));
- cmd.call();
- } catch (GitAPIException e) {
- throw new GitException(e);
- } catch (IOException e) {
- throw new GitException(e);
- } finally {
- if (walk != null) walk.dispose();
- if (or != null) or.release();
- if (repo != null) repo.close();
- }
- }
- /**
- * Git-notes normalizes newlines.
- *
- * This behaviour is reverse engineered from limited experiments, so it may be incomplete.
- */
- private String normalizeNote(String note) {
- note = note.trim();
- note = note.replaceAll("\r\n","\n").replaceAll("\n{3,}","\n\n");
- note += "\n";
- return note;
- }
- private String qualifyNotesNamespace(String namespace) {
- if (!namespace.startsWith("refs/")) namespace = "refs/notes/"+namespace;
- return namespace;
- }
- /** {@inheritDoc} */
- public void appendNote(String note, String namespace) throws GitException {
- Repository repo = null;
- RevWalk walk = null;
- ObjectReader or = null;
- try {
- repo = getRepository();
- ObjectId head = repo.resolve(HEAD); // commit to put a note on
- ShowNoteCommand cmd = git(repo).notesShow();
- cmd.setNotesRef(qualifyNotesNamespace(namespace));
- or = repo.newObjectReader();
- walk = new RevWalk(or);
- cmd.setObjectId(walk.parseAny(head));
- Note n = cmd.call();
- if (n==null) {
- addNote(note,namespace);
- } else {
- ObjectLoader ol = or.open(n.getData());
- StringWriter sw = new StringWriter();
- IOUtils.copy(new InputStreamReader(ol.openStream(),CHARSET),sw);
- sw.write("\n");
- addNote(sw.toString() + normalizeNote(note), namespace);
- }
- } catch (GitAPIException e) {
- throw new GitException(e);
- } catch (IOException e) {
- throw new GitException(e);
- } finally {
- if (walk != null) walk.dispose();
- if (or != null) or.release();
- if (repo != null) repo.close();
- }
- }
- /**
- * changelog.
- *
- * @return a {@link org.jenkinsci.plugins.gitclient.ChangelogCommand} object.
- */
- public ChangelogCommand changelog() {
- return new ChangelogCommand() {
- Repository repo = getRepository();
- ObjectReader or = repo.newObjectReader();
- RevWalk walk = new RevWalk(or);
- Writer out;
- boolean hasIncludedRev = false;
- public ChangelogCommand excludes(String rev) {
- try {
- return excludes(repo.resolve(rev));
- } catch (IOException e) {
- throw new GitException(e);
- }
- }
- public ChangelogCommand excludes(ObjectId rev) {
- try {
- walk.markUninteresting(walk.lookupCommit(rev));
- return this;
- } catch (IOException e) {
- throw new GitException(e);
- }
- }
- public ChangelogCommand includes(String rev) {
- try {
- includes(repo.resolve(rev));
- hasIncludedRev = true;
- return this;
- } catch (IOException e) {
- throw new GitException(e);
- }
- }
- public ChangelogCommand includes(ObjectId rev) {
- try {
- walk.markStart(walk.lookupCommit(rev));
- hasIncludedRev = true;
- return this;
- } catch (IOException e) {
- throw new GitException(e);
- }
- }
- public ChangelogCommand to(Writer w) {
- this.out = w;
- return this;
- }
- public ChangelogCommand max(int n) {
- walk.setRevFilter(MaxCountRevFilter.create(n));
- return this;
- }
- private void closeResources() {
- walk.dispose();
- or.release();
- repo.close();
- }
- public void abort() {
- closeResources();
- }
- /** Execute the changelog command. Assumed that this is
- * only performed once per instance of this object.
- * Resources opened by this ChangelogCommand object are
- * closed at exit from the execute method. Either execute
- * or abort must be called for each ChangelogCommand or
- * files will remain open.
- */
- public void execute() throws GitException, InterruptedException {
- PrintWriter pw = new PrintWriter(out,false);
- try {
- RawFormatter formatter= new RawFormatter();
- if (!hasIncludedRev) {
- /* If no rev has been included, assume HEAD */
- this.includes("HEAD");
- }
- for (RevCommit commit : walk) {
- // git whatachanged doesn't show the merge commits unless -m is given
- if (commit.getParentCount()>1) continue;
- formatter.format(commit, null, pw, true);
- }
- } catch (IOException e) {
- throw new GitException(e);
- } finally {
- closeResources();
- pw.flush();
- }
- }
- };
- }
- /**
- * Formats {@link RevCommit}.
- */
- class RawFormatter {
- private boolean hasNewPath(DiffEntry d) {
- return d.getChangeType()==ChangeType.COPY || d.getChangeType()==ChangeType.RENAME;
- }
- private String statusOf(DiffEntry d) {
- switch (d.getChangeType()) {
- case ADD: return "A";
- case MODIFY: return "M";
- case DELETE: return "D";
- case RENAME: return "R"+d.getScore();
- case COPY: return "C"+d.getScore();
- default:
- throw new AssertionError("Unexpected change type: "+d.getChangeType());
- }
- }
- public static final String ISO_8601 = "yyyy-MM-dd'T'HH:mm:ssZ";
- /**
- * Formats a commit into the raw format.
- *
- * @param commit
- * Commit to format.
- * @param parent
- * Optional parent commit to produce the diff against. This only matters
- * for merge commits, and git-log/git-whatchanged/etc behaves differently with respect to this.
- */
- void format(RevCommit commit, @Nullable RevCommit parent, PrintWriter pw, Boolean useRawOutput) throws IOException {
- if (parent!=null)
- pw.printf("commit %s (from %s)\n", commit.name(), parent.name());
- else
- pw.printf("commit %s\n", commit.name());
- pw.printf("tree %s\n", commit.getTree().name());
- for (RevCommit p : commit.getParents())
- pw.printf("parent %s\n",p.name());
- FastDateFormat iso = FastDateFormat.getInstance(ISO_8601);
- PersonIdent a = commit.getAuthorIdent();
- pw.printf("author %s <%s> %s\n", a.getName(), a.getEmailAddress(), iso.format(a.getWhen()));
- PersonIdent c = commit.getCommitterIdent();
- pw.printf("committer %s <%s> %s\n", c.getName(), c.getEmailAddress(), iso.format(c.getWhen()));
- // indent commit messages by 4 chars
- String msg = commit.getFullMessage();
- if (msg.endsWith("\n")) msg=msg.substring(0,msg.length()-1);
- msg = msg.replace("\n","\n ");
- msg="\n "+msg+"\n";
- pw.println(msg);
- // see man git-diff-tree for the format
- Repository repo = getRepository();
- ObjectReader or = repo.newObjectReader();
- TreeWalk tw = new TreeWalk(or);
- if (parent != null) {
- /* Caller provided a parent commit, use it */
- tw.reset(parent.getTree(), commit.getTree());
- } else {
- if (commit.getParentCount() > 0) {
- /* Caller failed to provide parent, but a parent
- * is available, so use the parent in the walk
- */
- tw.reset(commit.getParent(0).getTree(), commit.getTree());
- } else {
- /* First commit in repo has 0 parent count, but
- * the TreeWalk requires exactly two nodes for its
- * walk. Use the same node twice to satisfy
- * TreeWalk. See JENKINS-22343 for details.
- */
- tw.reset(commit.getTree(), commit.getTree());
- }
- }
- tw.setRecursive(true);
- tw.setFilter(TreeFilter.ANY_DIFF);
- final RenameDetector rd = new RenameDetector(repo);
- rd.reset();
- rd.addAll(DiffEntry.scan(tw));
- List<DiffEntry> diffs = rd.compute(or, null);
- tw.release();
- or.release();
- repo.close();
- if (useRawOutput) {
- for (DiffEntry diff : diffs) {
- pw.printf(":%06o %06o %s %s %s\t%s",
- diff.getOldMode().getBits(),
- diff.getNewMode().getBits(),
- diff.getOldId().name(),
- diff.getNewId().name(),
- statusOf(diff),
- diff.getChangeType()==ChangeType.ADD ? diff.getNewPath() : diff.getOldPath());
- if (hasNewPath(diff)) {
- pw.printf(" %s",diff.getNewPath()); // copied to
- }
- pw.println();
- pw.println();
- }
- }
- }
- }
- /**
- * clean.
- *
- * @throws hudson.plugins.git.GitException if underlying git operation fails.
- */
- public void clean() throws GitException {
- Repository repo = null;
- try {
- repo = getRepository();
- Git git = git(repo);
- git.reset().setMode(HARD).call();
- git.clean().setCleanDirectories(true).setIgnore(false).call();
- } catch (GitAPIException e) {
- throw new GitException(e);
- } finally {
- if (repo != null) repo.close();
- }
- }
- /**
- * clone_.
- *
- * @return a {@link org.jenkinsci.plugins.gitclient.CloneCommand} object.
- */
- public CloneCommand clone_() {
- return new CloneCommand() {
- String url;
- String remote = Constants.DEFAULT_REMOTE_NAME;
- String reference;
- Integer timeout;
- boolean shared;
- boolean tags = true;
- List<RefSpec> refspecs;
- public CloneCommand url(String url) {
- this.url = url;
- return this;
- }
- public CloneCommand repositoryName(String name) {
- this.remote = name;
- return this;
- }
- public CloneCommand shallow() {
- listener.getLogger().println("[WARNING] JGit doesn't support shallow clone. This flag is ignored");
- return this;
- }
- public CloneCommand shared() {
- this.shared = true;
- return this;
- }
- public CloneCommand reference(String reference) {
- this.reference = reference;
- return this;
- }
- public CloneCommand refspecs(List<RefSpec> refspecs) {
- this.refspecs = new ArrayList<RefSpec>(refspecs);
- return this;
- }
- public CloneCommand timeout(Integer timeout) {
- this.timeout = timeout;
- return this;
- }
-