Skip to content

Commit

Permalink
Merge pull request #433 from jglick/retrieve-revision-JENKINS-31155
Browse files Browse the repository at this point in the history
[JENKINS-31155] Ability to retrieve a single revision
  • Loading branch information
MarkEWaite committed Oct 29, 2016
2 parents 2025127 + 2ef42a2 commit 0c6d939
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 96 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -173,7 +173,7 @@
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>scm-api</artifactId>
<version>1.2</version>
<version>1.3</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/hudson/plugins/git/UserRemoteConfig.java
Expand Up @@ -6,6 +6,7 @@
import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import hudson.EnvVars;
import hudson.Extension;
import hudson.Util;
Expand Down Expand Up @@ -46,7 +47,7 @@ public class UserRemoteConfig extends AbstractDescribableImpl<UserRemoteConfig>
private String credentialsId;

@DataBoundConstructor
public UserRemoteConfig(String url, String name, String refspec, String credentialsId) {
public UserRemoteConfig(String url, String name, String refspec, @CheckForNull String credentialsId) {
this.url = fixEmptyAndTrim(url);
this.name = fixEmpty(name);
this.refspec = fixEmpty(refspec);
Expand All @@ -69,6 +70,7 @@ public String getUrl() {
}

@Exported
@CheckForNull
public String getCredentialsId() {
return credentialsId;
}
Expand Down
234 changes: 140 additions & 94 deletions src/main/java/jenkins/plugins/git/AbstractGitSCMSource.java
Expand Up @@ -70,6 +70,7 @@
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.jenkinsci.plugins.gitclient.FetchCommand;
import org.jenkinsci.plugins.gitclient.Git;
import org.jenkinsci.plugins.gitclient.GitClient;

Expand All @@ -80,7 +81,9 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
Expand All @@ -107,6 +110,7 @@ public AbstractGitSCMSource(String id) {
super(id);
}

@CheckForNull
public abstract String getCredentialsId();

public abstract String getRemote();
Expand Down Expand Up @@ -162,9 +166,10 @@ protected GitTool resolveGitTool() {
return tool;
}

@CheckForNull
@Override
protected SCMRevision retrieve(@NonNull SCMHead head, @NonNull TaskListener listener)
private interface Retriever<T> {
T run(GitClient client, String remoteName) throws IOException, InterruptedException;
}
private <T> T doRetrieve(Retriever<T> retriever, @NonNull TaskListener listener, boolean prune)
throws IOException, InterruptedException {
String cacheEntry = getCacheEntry();
Lock cacheLock = getCacheLock(cacheEntry);
Expand All @@ -185,22 +190,42 @@ protected SCMRevision retrieve(@NonNull SCMHead head, @NonNull TaskListener list
String remoteName = getRemoteName();
listener.getLogger().println("Setting " + remoteName + " to " + getRemote());
client.setRemoteUrl(remoteName, getRemote());
listener.getLogger().println("Fetching " + remoteName + "...");
List<RefSpec> refSpecs = getRefSpecs();
client.fetch(remoteName, refSpecs.toArray(new RefSpec[refSpecs.size()]));
// we don't prune remotes here, as we just want one head's revision
for (Branch b : client.getRemoteBranches()) {
String branchName = StringUtils.removeStart(b.getName(), remoteName + "/");
if (branchName.equals(head.getName())) {
return new SCMRevisionImpl(head, b.getSHA1String());
}
listener.getLogger().println((prune ? "Fetching & pruning " : "Fetching ") + remoteName + "...");
FetchCommand fetch = client.fetch_();
if (prune) {
fetch = fetch.prune();
}
return null;
URIish remoteURI = null;
try {
remoteURI = new URIish(remoteName);
} catch (URISyntaxException ex) {
listener.getLogger().println("URI syntax exception for '" + remoteName + "' " + ex);
}
fetch.from(remoteURI, getRefSpecs()).execute();
return retriever.run(client, remoteName);
} finally {
cacheLock.unlock();
}
}

@CheckForNull
@Override
protected SCMRevision retrieve(@NonNull final SCMHead head, @NonNull TaskListener listener)
throws IOException, InterruptedException {
return doRetrieve(new Retriever<SCMRevision>() {
@Override
public SCMRevision run(GitClient client, String remoteName) throws IOException, InterruptedException {
for (Branch b : client.getRemoteBranches()) {
String branchName = StringUtils.removeStart(b.getName(), remoteName + "/");
if (branchName.equals(head.getName())) {
return new SCMRevisionImpl(head, b.getSHA1String());
}
}
return null;
}
}, listener, /* we don't prune remotes here, as we just want one head's revision */false);
}

private static void _close(@NonNull Object walk) {
java.lang.reflect.Method closeMethod;
try {
Expand Down Expand Up @@ -262,97 +287,113 @@ private void _release(RevWalk walk) {
@NonNull
@Override
protected void retrieve(@NonNull final SCMHeadObserver observer,
@NonNull TaskListener listener)
@NonNull final TaskListener listener)
throws IOException, InterruptedException {
String cacheEntry = getCacheEntry();
Lock cacheLock = getCacheLock(cacheEntry);
cacheLock.lock();
try {
File cacheDir = getCacheDir(cacheEntry);
Git git = Git.with(listener, new EnvVars(EnvVars.masterEnvVars)).in(cacheDir);
GitTool tool = resolveGitTool();
if (tool != null) {
git.using(tool.getGitExe());
}
GitClient client = git.getClient();
client.addDefaultCredentials(getCredentials());
if (!client.hasGitRepo()) {
listener.getLogger().println("Creating git repository in " + cacheDir);
client.init();
}
String remoteName = getRemoteName();
listener.getLogger().println("Setting " + remoteName + " to " + getRemote());
client.setRemoteUrl(remoteName, getRemote());
listener.getLogger().println("Fetching & pruning " + remoteName + "...");
URIish remoteURI = null;
try {
remoteURI = new URIish(remoteName);
} catch (URISyntaxException ex) {
listener.getLogger().println("URI syntax exception for '" + remoteName + "' " + ex);
}
client.fetch_().prune().from(remoteURI, getRefSpecs()).execute();
final Repository repository = client.getRepository();
listener.getLogger().println("Getting remote branches...");
SCMSourceCriteria branchCriteria = getCriteria();
RevWalk walk = new RevWalk(repository);
try {
walk.setRetainBody(false);
for (Branch b : client.getRemoteBranches()) {
if (!b.getName().startsWith(remoteName + "/")) {
continue;
}
final String branchName = StringUtils.removeStart(b.getName(), remoteName + "/");
listener.getLogger().println("Checking branch " + branchName);
if (isExcluded(branchName)){
continue;
}
if (branchCriteria != null) {
RevCommit commit = walk.parseCommit(b.getSHA1());
final long lastModified = TimeUnit.SECONDS.toMillis(commit.getCommitTime());
final RevTree tree = commit.getTree();
SCMSourceCriteria.Probe probe = new SCMSourceCriteria.Probe() {
@Override
public String name() {
return branchName;
}
doRetrieve(new Retriever<Void>() {
@Override
public Void run(GitClient client, String remoteName) throws IOException, InterruptedException {
final Repository repository = client.getRepository();
listener.getLogger().println("Getting remote branches...");
SCMSourceCriteria branchCriteria = getCriteria();
RevWalk walk = new RevWalk(repository);
try {
walk.setRetainBody(false);
for (Branch b : client.getRemoteBranches()) {
if (!b.getName().startsWith(remoteName + "/")) {
continue;
}
final String branchName = StringUtils.removeStart(b.getName(), remoteName + "/");
listener.getLogger().println("Checking branch " + branchName);
if (isExcluded(branchName)) {
continue;
}
if (branchCriteria != null) {
RevCommit commit = walk.parseCommit(b.getSHA1());
final long lastModified = TimeUnit.SECONDS.toMillis(commit.getCommitTime());
final RevTree tree = commit.getTree();
SCMSourceCriteria.Probe probe = new SCMSourceCriteria.Probe() {
@Override
public String name() {
return branchName;
}

@Override
public long lastModified() {
return lastModified;
}
@Override
public long lastModified() {
return lastModified;
}

@Override
public boolean exists(@NonNull String path) throws IOException {
TreeWalk tw = TreeWalk.forPath(repository, path, tree);
try {
return tw != null;
} finally {
_release(tw);
@Override
public boolean exists(@NonNull String path) throws IOException {
TreeWalk tw = TreeWalk.forPath(repository, path, tree);
try {
return tw != null;
} finally {
_release(tw);
}
}
};
if (branchCriteria.isHead(probe, listener)) {
listener.getLogger().println("Met criteria");
} else {
listener.getLogger().println("Does not meet criteria");
continue;
}
};
if (branchCriteria.isHead(probe, listener)) {
listener.getLogger().println("Met criteria");
} else {
listener.getLogger().println("Does not meet criteria");
continue;
}
SCMHead head = new SCMHead(branchName);
SCMRevision hash = new SCMRevisionImpl(head, b.getSHA1String());
observer.observe(head, hash);
if (!observer.isObserving()) {
return null;
}
}
SCMHead head = new SCMHead(branchName);
SCMRevision hash = new SCMRevisionImpl(head, b.getSHA1String());
observer.observe(head, hash);
if (!observer.isObserving()) {
return;
} finally {
_release(walk);
}

listener.getLogger().println("Done.");
return null;
}
}, listener, true);
}

@CheckForNull
@Override
protected SCMRevision retrieve(@NonNull final String revision, @NonNull final TaskListener listener) throws IOException, InterruptedException {
return doRetrieve(new Retriever<SCMRevision>() {
@Override
public SCMRevision run(GitClient client, String remoteName) throws IOException, InterruptedException {
String hash;
try {
hash = client.revParse(revision).name();
} catch (GitException x) {
// Try prepending origin/ in case it was a branch.
try {
hash = client.revParse("origin/" + revision).name();
} catch (GitException x2) {
listener.getLogger().println(x.getMessage());
listener.getLogger().println(x2.getMessage());
return null;
}
}
} finally {
_release(walk);
return new SCMRevisionImpl(new SCMHead(revision), hash);
}
}, listener, false);
}

listener.getLogger().println("Done.");
} finally {
cacheLock.unlock();
}
@CheckForNull
@Override
protected Set<String> retrieveRevisions(@NonNull final TaskListener listener) throws IOException, InterruptedException {
return doRetrieve(new Retriever<Set<String>>() {
@Override
public Set<String> run(GitClient client, String remoteName) throws IOException, InterruptedException {
Set<String> revisions = new HashSet<String>();
for (Branch branch : client.getRemoteBranches()) {
revisions.add(branch.getName().replaceFirst("^origin/", ""));
}
revisions.addAll(client.getTagNames("*"));
return revisions;
}
}, listener, false);
}

protected String getCacheEntry() {
Expand Down Expand Up @@ -383,12 +424,17 @@ protected static Lock getCacheLock(String cacheEntry) {
return cacheLock;
}

@CheckForNull
protected StandardUsernameCredentials getCredentials() {
String credentialsId = getCredentialsId();
if (credentialsId == null) {
return null;
}
return CredentialsMatchers
.firstOrNull(
CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class, getOwner(),
ACL.SYSTEM, URIRequirementBuilder.fromUri(getRemote()).build()),
CredentialsMatchers.allOf(CredentialsMatchers.withId(getCredentialsId()),
CredentialsMatchers.allOf(CredentialsMatchers.withId(credentialsId),
GitClient.CREDENTIALS_MATCHER));
}

Expand Down

0 comments on commit 0c6d939

Please sign in to comment.