Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FIXED JENKINS-47824] Looks like we have to do much more hoop jumping…
… to correctly resolve things
  • Loading branch information
stephenc committed Nov 4, 2017
1 parent 2b072d5 commit addcc0a
Show file tree
Hide file tree
Showing 2 changed files with 218 additions and 9 deletions.
142 changes: 133 additions & 9 deletions src/main/java/jenkins/plugins/git/AbstractGitSCMSource.java
Expand Up @@ -64,6 +64,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
Expand Down Expand Up @@ -688,23 +689,146 @@ protected SCMRevision retrieve(@NonNull final String revision, @NonNull final Ta
}
return null;
}
// first we need to figure out what the revision is. There are six possibilities:
// 1. A branch name (if we have that we can return quickly)
// 2. A tag name (if we have that we will need to fetch the tag to resolve the tag date)
// 3. A short/full revision hash that is the head revision of a branch (if we have that we can return quickly)
// 4. A short revision hash that is the head revision of a branch (if we have that we can return quickly)
// 5. A short/full revision hash for a tag (we'll need to fetch the tag to resolve the tag date)
// 6. A short/full revision hash that is not the head revision of a branch (we'll need to fetch everything to
// try and resolve the hash from the history of one of the heads)
Git git = Git.with(listener, new EnvVars(EnvVars.masterEnvVars));
GitTool tool = resolveGitTool(context.gitTool());
if (tool != null) {
git.using(tool.getGitExe());
}
GitClient client = git.getClient();
client.addDefaultCredentials(getCredentials());
listener.getLogger().printf("Attempting to resolve %s from remote references...%n", revision);
Map<String, ObjectId> remoteReferences = client.getRemoteReferences(
getRemote(), null, true, true
);
String tagName = null;
Set<String> shortNameMatches = new TreeSet<>();
String shortHashMatch = null;
Set<String> fullTagMatches = new TreeSet<>();
for (Map.Entry<String,ObjectId> entry: remoteReferences.entrySet()) {
String name = entry.getKey();
String rev = entry.getValue().name();
if (name.equals(Constants.R_HEADS + revision)) {
listener.getLogger().printf("Found match: %s revision %s%n", name, rev);
// WIN!
return new SCMRevisionImpl(new SCMHead(revision), rev);
}
if (name.equals(Constants.R_TAGS+revision)) {
listener.getLogger().printf("Found match: %s revision %s%n", name, rev);
// WIN but not the good kind
tagName = revision;
context.wantBranches(false);
context.wantTags(true);
context.withoutRefSpecs();
break;
}
if (name.startsWith(Constants.R_HEADS) && revision.equalsIgnoreCase(rev)) {
listener.getLogger().printf("Found match: %s revision %s%n", name, rev);
// WIN!
return new SCMRevisionImpl(new SCMHead(StringUtils.removeStart(name, Constants.R_HEADS)), rev);
}
if (name.startsWith(Constants.R_TAGS) && revision.equalsIgnoreCase(rev)) {
listener.getLogger().printf("Candidate match: %s revision %s%n", name, rev);
// WIN but let's see if a branch also matches as that would save a fetch
fullTagMatches.add(name);
continue;
}
if (rev.toLowerCase(Locale.ENGLISH).startsWith(revision.toLowerCase(Locale.ENGLISH))) {
shortNameMatches.add(name);
if (shortHashMatch == null) {
listener.getLogger().printf("Candidate partial match: %s revision %s%n", name, rev);
shortHashMatch = rev;
} else {
listener.getLogger().printf("Candidate partial match: %s revision %s%n", name, rev);
listener.getLogger().printf("Cannot resolve ambiguous short revision %s%n", revision);
return null;
}
}
}
if (!fullTagMatches.isEmpty()) {
// we just want a tag so we can do a minimal fetch
String name = StringUtils.removeStart(fullTagMatches.iterator().next(), Constants.R_TAGS);
listener.getLogger().printf("Selected match: %s revision %s%n", name, shortHashMatch);
tagName = name;
context.wantBranches(false);
context.wantTags(true);
context.withoutRefSpecs();
}
if (shortHashMatch != null) {
// woot this seems unambiguous
for (String name: shortNameMatches) {
if (name.startsWith(Constants.R_HEADS)) {
listener.getLogger().printf("Selected match: %s revision %s%n", name, shortHashMatch);
// WIN it's also a branch
return new SCMRevisionImpl(new SCMHead(StringUtils.removeStart(name, Constants.R_HEADS)),
shortHashMatch);
}
}
// ok pick a tag so we can do minimal fetch
String name = StringUtils.removeStart(shortNameMatches.iterator().next(), Constants.R_TAGS);
listener.getLogger().printf("Selected match: %s revision %s%n", name, shortHashMatch);
tagName = name;
context.wantBranches(false);
context.wantTags(true);
context.withoutRefSpecs();
}
if (tagName != null) {
listener.getLogger().println(
"Resolving tag commit... (remote references may be a lightweight tag or an annotated tag)");
final String tagRef = Constants.R_TAGS+tagName;
return doRetrieve(new Retriever<SCMRevision>() {
@Override
public SCMRevision run(GitClient client, String remoteName) throws IOException,
InterruptedException {
final Repository repository = client.getRepository();
try (RevWalk walk = new RevWalk(repository)) {
ObjectId ref = client.revParse(tagRef);
RevCommit commit = walk.parseCommit(ref);
long lastModified = TimeUnit.SECONDS.toMillis(commit.getCommitTime());
listener.getLogger().printf("Resolved tag %s revision %s%n", revision,
ref.getName());
return new GitTagSCMRevision(new GitTagSCMHead(revision, lastModified),
ref.name());
}
}
},
context,
listener, false);
}
// Pokémon!... Got to catch them all
listener.getLogger().printf("Could not find %s in remote references. "
+ "Pulling heads to local for deep search...%n", revision);
context.wantTags(true);
context.wantBranches(true);
return doRetrieve(new Retriever<SCMRevision>() {
@Override
public SCMRevision run(GitClient client, String remoteName) throws IOException, InterruptedException {
ObjectId objectId;
String hash;
try {
hash = client.revParse(revision).name();
} catch (GitException x) {
// Try prepending remote name in case it was a branch.
try {
hash = client.revParse(context.remoteName() + "/" + revision).name();
} catch (GitException x2) {
listener.getLogger().println(x.getMessage());
listener.getLogger().println(x2.getMessage());
objectId = client.revParse(revision);
hash = objectId.name();
List<Branch> branches = client.getBranchesContaining(hash, false);
if (branches.isEmpty()) {
listener.getLogger().printf("Could not find a branch containing commit %s%n",
hash);
return null;
}
String name = branches.get(0).getName();
listener.getLogger()
.printf("Selected match: %s revision %s%n", name, hash);
return new SCMRevisionImpl(new SCMHead(name), hash);
} catch (GitException x) {
x.printStackTrace(listener.error("Could not resolve %s", revision));
return null;
}
return new SCMRevisionImpl(new SCMHead(revision), hash);
}
},
context,
Expand Down
85 changes: 85 additions & 0 deletions src/test/java/jenkins/plugins/git/AbstractGitSCMSourceTest.java
Expand Up @@ -226,6 +226,91 @@ public void retrieveRevisions() throws Exception {
assertThat(source.fetchRevisions(listener), containsInAnyOrder("dev", "master", "annotated", "lightweight"));
}

@Issue("JENKINS-47824")
@Test
public void retrieveByName() throws Exception {
sampleRepo.init();
String masterHash = sampleRepo.head();
sampleRepo.git("checkout", "-b", "dev");
sampleRepo.write("file", "modified");
sampleRepo.git("commit", "--all", "--message=dev");
sampleRepo.git("tag", "v1");
String v1Hash = sampleRepo.head();
sampleRepo.write("file", "modified2");
sampleRepo.git("commit", "--all", "--message=dev2");
sampleRepo.git("tag", "-a", "v2", "-m", "annotated");
String v2Hash = sampleRepo.head();
sampleRepo.write("file", "modified3");
sampleRepo.git("commit", "--all", "--message=dev3");
String devHash = sampleRepo.head();
GitSCMSource source = new GitSCMSource(sampleRepo.toString());
source.setTraits(new ArrayList<SCMSourceTrait>());

TaskListener listener = StreamTaskListener.fromStderr();

listener.getLogger().println("\n=== fetch('master') ===\n");
SCMRevision rev = source.fetch("master", listener);
assertThat(rev, instanceOf(AbstractGitSCMSource.SCMRevisionImpl.class));
assertThat(((AbstractGitSCMSource.SCMRevisionImpl)rev).getHash(), is(masterHash));
listener.getLogger().println("\n=== fetch('dev') ===\n");
rev = source.fetch("dev", listener);
assertThat(rev, instanceOf(AbstractGitSCMSource.SCMRevisionImpl.class));
assertThat(((AbstractGitSCMSource.SCMRevisionImpl)rev).getHash(), is(devHash));
listener.getLogger().println("\n=== fetch('v1') ===\n");
rev = source.fetch("v1", listener);
assertThat(rev, instanceOf(GitTagSCMRevision.class));
assertThat(((GitTagSCMRevision)rev).getHash(), is(v1Hash));
listener.getLogger().println("\n=== fetch('v2') ===\n");
rev = source.fetch("v2", listener);
assertThat(rev, instanceOf(GitTagSCMRevision.class));
assertThat(((GitTagSCMRevision)rev).getHash(), is(v2Hash));

listener.getLogger().printf("%n=== fetch('%s') ===%n%n", masterHash);
rev = source.fetch(masterHash, listener);
assertThat(rev, instanceOf(AbstractGitSCMSource.SCMRevisionImpl.class));
assertThat(((AbstractGitSCMSource.SCMRevisionImpl) rev).getHash(), is(masterHash));
assertThat(rev.getHead().getName(), is("master"));

listener.getLogger().printf("%n=== fetch('%s') ===%n%n", masterHash.substring(0, 10));
rev = source.fetch(masterHash.substring(0, 10), listener);
assertThat(rev, instanceOf(AbstractGitSCMSource.SCMRevisionImpl.class));
assertThat(((AbstractGitSCMSource.SCMRevisionImpl) rev).getHash(), is(masterHash));
assertThat(rev.getHead().getName(), is("master"));

listener.getLogger().printf("%n=== fetch('%s') ===%n%n", devHash);
rev = source.fetch(devHash, listener);
assertThat(rev, instanceOf(AbstractGitSCMSource.SCMRevisionImpl.class));
assertThat(((AbstractGitSCMSource.SCMRevisionImpl) rev).getHash(), is(devHash));
assertThat(rev.getHead().getName(), is("dev"));

listener.getLogger().printf("%n=== fetch('%s') ===%n%n", devHash.substring(0, 10));
rev = source.fetch(devHash.substring(0, 10), listener);
assertThat(rev, instanceOf(AbstractGitSCMSource.SCMRevisionImpl.class));
assertThat(((AbstractGitSCMSource.SCMRevisionImpl) rev).getHash(), is(devHash));
assertThat(rev.getHead().getName(), is("dev"));

listener.getLogger().printf("%n=== fetch('%s') ===%n%n", v1Hash);
rev = source.fetch(v1Hash, listener);
assertThat(rev, instanceOf(AbstractGitSCMSource.SCMRevisionImpl.class));
assertThat(((AbstractGitSCMSource.SCMRevisionImpl) rev).getHash(), is(v1Hash));

listener.getLogger().printf("%n=== fetch('%s') ===%n%n", v1Hash.substring(0, 10));
rev = source.fetch(v1Hash.substring(0, 10), listener);
assertThat(rev, instanceOf(AbstractGitSCMSource.SCMRevisionImpl.class));
assertThat(((AbstractGitSCMSource.SCMRevisionImpl) rev).getHash(), is(v1Hash));

listener.getLogger().printf("%n=== fetch('%s') ===%n%n", v2Hash);
rev = source.fetch(v2Hash, listener);
assertThat(rev, instanceOf(AbstractGitSCMSource.SCMRevisionImpl.class));
assertThat(((AbstractGitSCMSource.SCMRevisionImpl) rev).getHash(), is(v2Hash));

listener.getLogger().printf("%n=== fetch('%s') ===%n%n", v2Hash.substring(0, 10));
rev = source.fetch(v2Hash.substring(0, 10), listener);
assertThat(rev, instanceOf(AbstractGitSCMSource.SCMRevisionImpl.class));
assertThat(((AbstractGitSCMSource.SCMRevisionImpl) rev).getHash(), is(v2Hash));

}

public static abstract class ActionableSCMSourceOwner extends Actionable implements SCMSourceOwner {

}
Expand Down

0 comments on commit addcc0a

Please sign in to comment.