Skip to content

Commit

Permalink
Merge pull request #487 from jenkinsci/jenkins-40834
Browse files Browse the repository at this point in the history
[JENKINS-40834] Report the primary branch
  • Loading branch information
MarkEWaite committed Mar 25, 2017
2 parents a2ac1ae + d2c51dc commit 497870f
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 2 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -147,7 +147,7 @@
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>git-client</artifactId>
<version>2.3.0</version>
<version>2.3.1-20170322.110355-2</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
Expand Down
91 changes: 90 additions & 1 deletion src/main/java/jenkins/plugins/git/AbstractGitSCMSource.java
@@ -1,7 +1,7 @@
/*
* The MIT License
*
* Copyright (c) 2013-2014, CloudBees, Inc., Stephen Connolly, Amadeus IT Group.
* Copyright (c) 2013-2017, CloudBees, Inc., Stephen Connolly, Amadeus IT Group.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -34,6 +34,8 @@
import hudson.EnvVars;
import hudson.Extension;
import hudson.Util;
import hudson.model.Action;
import hudson.model.Actionable;
import hudson.model.Item;
import hudson.model.TaskListener;
import hudson.plugins.git.Branch;
Expand All @@ -52,8 +54,11 @@
import hudson.plugins.git.util.BuildChooserContext;
import hudson.plugins.git.util.BuildChooserDescriptor;
import hudson.plugins.git.util.BuildData;
import hudson.remoting.VirtualChannel;
import hudson.scm.SCM;
import hudson.security.ACL;
import java.util.Map;
import java.util.TreeSet;
import jenkins.model.Jenkins;
import jenkins.scm.api.SCMFile;
import jenkins.scm.api.SCMHead;
Expand All @@ -64,11 +69,16 @@
import jenkins.scm.api.SCMRevision;
import jenkins.scm.api.SCMSource;
import jenkins.scm.api.SCMSourceCriteria;
import jenkins.scm.api.SCMSourceEvent;
import jenkins.scm.api.SCMSourceOwner;
import jenkins.scm.api.metadata.PrimaryInstanceMetadataAction;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.SymbolicRef;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
Expand Down Expand Up @@ -96,6 +106,7 @@
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.eclipse.jgit.transport.URIish;
import org.jenkinsci.plugins.gitclient.RepositoryCallback;

/**
* @author Stephen Connolly
Expand Down Expand Up @@ -372,6 +383,84 @@ public Set<String> run(GitClient client, String remoteName) throws IOException,
}, listener, false);
}

@NonNull
@Override
protected List<Action> retrieveActions(@CheckForNull SCMSourceEvent event, @NonNull TaskListener listener)
throws IOException, InterruptedException {
return doRetrieve(new Retriever<List<Action>>() {
@Override
public List<Action> run(GitClient client, String remoteName) throws IOException, InterruptedException {
Map<String, String> symrefs = client.getRemoteSymbolicReferences(getRemote(), null);
if (symrefs.containsKey(Constants.HEAD)) {
// Hurrah! The Server is Git 1.8.5 or newer and our client has symref reporting
String target = symrefs.get(Constants.HEAD);
if (target.startsWith(Constants.R_HEADS)) {
// shorten standard names
target = target.substring(Constants.R_HEADS.length());
}
List<Action> result = new ArrayList<>();
if (StringUtils.isNotBlank(target)) {
result.add(new GitRemoteHeadRefAction(getRemote(), target));
}
return result;
}
// Ok, now we do it the old-school way... see what ref has the same hash as HEAD
// I think we will still need to keep this code path even if JGit implements
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=514052 as there is always the potential that
// the remote server is Git 1.8.4 or earlier
Map<String, ObjectId> remoteReferences = client.getRemoteReferences(getRemote(), null, false, false);
if (remoteReferences.containsKey(Constants.HEAD)) {
ObjectId head = remoteReferences.get(Constants.HEAD);
Set<String> names = new TreeSet<>();
for (Map.Entry<String, ObjectId> entry: remoteReferences.entrySet()) {
if (entry.getKey().equals(Constants.HEAD)) continue;
if (head.equals(entry.getValue())) {
names.add(entry.getKey());
}
}
// if there is one and only one match, that's the winner
if (names.size() == 1) {
String target = names.iterator().next();
if (target.startsWith(Constants.R_HEADS)) {
// shorten standard names
target = target.substring(Constants.R_HEADS.length());
}
List<Action> result = new ArrayList<>();
if (StringUtils.isNotBlank(target)) {
result.add(new GitRemoteHeadRefAction(getRemote(), target));
}
return result;
}
// if there are multiple matches, prefer `master`
if (names.contains(Constants.R_HEADS + Constants.MASTER)) {
List<Action> result = new ArrayList<Action>();
result.add(new GitRemoteHeadRefAction(getRemote(), Constants.MASTER));
return result;
}
}
// Give up, there's no way to get the primary branch
return new ArrayList<>();
}
}, listener, false);
}

@NonNull
@Override
protected List<Action> retrieveActions(@NonNull SCMHead head, @CheckForNull SCMHeadEvent event,
@NonNull TaskListener listener) throws IOException, InterruptedException {
SCMSourceOwner owner = getOwner();
if (owner instanceof Actionable) {
for (GitRemoteHeadRefAction a: ((Actionable) owner).getActions(GitRemoteHeadRefAction.class)) {
if (getRemote().equals(a.getRemote())) {
if (head.getName().equals(a.getName())) {
return Collections.<Action>singletonList(new PrimaryInstanceMetadataAction());
}
}
}
}
return Collections.emptyList();
}

protected String getCacheEntry() {
return getCacheEntry(getRemote());
}
Expand Down
67 changes: 67 additions & 0 deletions src/main/java/jenkins/plugins/git/GitRemoteHeadRefAction.java
@@ -0,0 +1,67 @@
package jenkins.plugins.git;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.model.InvisibleAction;
import java.io.Serializable;

/**
* @author Stephen Connolly
*/
public class GitRemoteHeadRefAction extends InvisibleAction implements Serializable {

private static final long serialVersionUID = 1L;

@NonNull
private final String remote;
@NonNull
private final String name;

public GitRemoteHeadRefAction(@NonNull String remote, @NonNull String name) {
this.remote = remote;
this.name = name;
}

@NonNull
public String getRemote() {
return remote;
}

@NonNull
public String getName() {
return name;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

GitRemoteHeadRefAction that = (GitRemoteHeadRefAction) o;

if (!remote.equals(that.remote)) {
return false;
}
return name.equals(that.name);
}

@Override
public int hashCode() {
int result = remote.hashCode();
result = 31 * result + name.hashCode();
return result;
}

@Override
public String toString() {
return "GitRemoteHeadRefAction{" +
"remote='" + remote + '\'' +
", name='" + name + '\'' +
'}';
}


}
66 changes: 66 additions & 0 deletions src/test/java/jenkins/plugins/git/AbstractGitSCMSourceTest.java
Expand Up @@ -2,6 +2,8 @@

import hudson.FilePath;
import hudson.Launcher;
import hudson.model.Action;
import hudson.model.Actionable;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.plugins.git.UserRemoteConfig;
Expand All @@ -12,18 +14,27 @@
import hudson.plugins.git.extensions.impl.LocalBranch;
import hudson.util.StreamTaskListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
import jenkins.scm.api.SCMHead;
import jenkins.scm.api.SCMRevision;
import jenkins.scm.api.SCMSource;
import static org.hamcrest.Matchers.*;

import jenkins.scm.api.SCMSourceOwner;
import jenkins.scm.api.metadata.PrimaryInstanceMetadataAction;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.mockito.Mockito;

import static org.junit.Assert.*;
import static org.mockito.Mockito.when;

/**
* Tests for {@link AbstractGitSCMSource}
Expand All @@ -34,6 +45,8 @@ public class AbstractGitSCMSourceTest {
public JenkinsRule r = new JenkinsRule();
@Rule
public GitSampleRepoRule sampleRepo = new GitSampleRepoRule();
@Rule
public GitSampleRepoRule sampleRepo2 = new GitSampleRepoRule();

// TODO AbstractGitSCMSourceRetrieveHeadsTest *sounds* like it would be the right place, but it does not in fact retrieve any heads!
@Issue("JENKINS-37482")
Expand All @@ -56,6 +69,59 @@ public void retrieveHeads() throws Exception {
assertEquals("[SCMHead{'dev'}, SCMHead{'dev2'}, SCMHead{'master'}]", source.fetch(listener).toString());
}

public static abstract class ActionableSCMSourceOwner extends Actionable implements SCMSourceOwner {

}

@Test
public void retrievePrimaryHead() throws Exception {
sampleRepo.init();
sampleRepo.write("file.txt", "");
sampleRepo.git("add", "file.txt");
sampleRepo.git("commit", "--all", "--message=add-empty-file");
sampleRepo.git("checkout", "-b", "new-primary");
sampleRepo.write("file.txt", "content");
sampleRepo.git("add", "file.txt");
sampleRepo.git("commit", "--all", "--message=add-file");
sampleRepo.git("checkout", "master");
sampleRepo.git("checkout", "-b", "dev");
sampleRepo.git("symbolic-ref", "HEAD", "refs/heads/new-primary");

SCMSource source = new GitSCMSource(null, sampleRepo.toString(), "", "*", "", true);
ActionableSCMSourceOwner owner = Mockito.mock(ActionableSCMSourceOwner.class);
when(owner.getSCMSource(source.getId())).thenReturn(source);
when(owner.getSCMSources()).thenReturn(Collections.singletonList(source));
source.setOwner(owner);
TaskListener listener = StreamTaskListener.fromStderr();
Map<String, SCMHead> headByName = new TreeMap<String, SCMHead>();
for (SCMHead h: source.fetch(listener)) {
headByName.put(h.getName(), h);
}
assertThat(headByName.keySet(), containsInAnyOrder("master", "dev", "new-primary"));
List<Action> actions = source.fetchActions(null, listener);
GitRemoteHeadRefAction refAction = null;
for (Action a: actions) {
if (a instanceof GitRemoteHeadRefAction) {
refAction = (GitRemoteHeadRefAction) a;
break;
}
}
assertThat(refAction, notNullValue());
assertThat(refAction.getName(), is("new-primary"));
when(owner.getAction(GitRemoteHeadRefAction.class)).thenReturn(refAction);
when(owner.getActions(GitRemoteHeadRefAction.class)).thenReturn(Collections.singletonList(refAction));
actions = source.fetchActions(headByName.get("new-primary"), null, listener);

PrimaryInstanceMetadataAction primary = null;
for (Action a: actions) {
if (a instanceof PrimaryInstanceMetadataAction) {
primary = (PrimaryInstanceMetadataAction) a;
break;
}
}
assertThat(primary, notNullValue());
}

@Issue("JENKINS-31155")
@Test
public void retrieveRevision() throws Exception {
Expand Down

0 comments on commit 497870f

Please sign in to comment.