Skip to content

Commit

Permalink
[JENKINS-40834] Add an API to resolve symbolic refs
Browse files Browse the repository at this point in the history
- Sadly JGit needs to try and guess
  • Loading branch information
stephenc committed Mar 21, 2017
1 parent 84f8ba8 commit 64f4303
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 1 deletion.
31 changes: 30 additions & 1 deletion src/main/java/org/jenkinsci/plugins/gitclient/CliGitAPIImpl.java
Expand Up @@ -2611,7 +2611,36 @@ public Map<String, ObjectId> getRemoteReferences(String url, String pattern, boo
return references;
}

//
@Override
public Map<String, String> getRemoteSymbolicReferences(String url, String pattern)
throws GitException, InterruptedException {
ArgumentListBuilder args = new ArgumentListBuilder("ls-remote");
args.add("--symref");
args.add(url);
if (pattern != null) {
args.add(pattern);
}

StandardCredentials cred = credentials.get(url);
if (cred == null) cred = defaultCredentials;

String result = launchCommandWithCredentials(args, null, cred, url);

Map<String, String> references = new HashMap<>();
String[] lines = result.split("\n");
Pattern symRefPattern = Pattern.compile("^ref:\\s+([^ ]+)\\s+([^ ]+)$");
for (String line : lines) {
Matcher matcher = symRefPattern.matcher(line);
if (matcher.matches()) {
references.put(matcher.group(2), matcher.group(1));
} else if (line.length() < 41) {
throw new GitException("unexpected ls-remote output " + line);
}
}
return references;
}

//
//
// Legacy Implementation of IGitAPI
//
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/org/jenkinsci/plugins/gitclient/GitClient.java
Expand Up @@ -619,6 +619,18 @@ public interface GitClient {
*/
Map<String, ObjectId> getRemoteReferences(String remoteRepoUrl, String pattern, boolean headsOnly, boolean tagsOnly) throws GitException, InterruptedException;

/**
* List symbolic references in a remote repository. Equivalent to <tt>git ls-remote --symref &lt;repository&gt;
* [&lt;refs&gt;]</tt>.
*
* @param remoteRepoUrl Remote repository URL.
* @param pattern Only references matching the given pattern are displayed.
* @return a map of references name and its underlying reference. Empty if none.
* @throws hudson.plugins.git.GitException if underlying git operation fails.
* @throws java.lang.InterruptedException if interrupted.
*/
Map<String, String> getRemoteSymbolicReferences(String remoteRepoUrl, String pattern) throws GitException, InterruptedException;

/**
* Retrieve commit object that is direct child for <tt>revName</tt> revision reference.
*
Expand Down
46 changes: 46 additions & 0 deletions src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java
Expand Up @@ -85,6 +85,7 @@
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryBuilder;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.lib.SymbolicRef;
import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.notes.Note;
import org.eclipse.jgit.revwalk.RevCommit;
Expand Down Expand Up @@ -784,6 +785,51 @@ public Map<String, ObjectId> getRemoteReferences(String url, String pattern, boo
return references;
}

@Override
public Map<String, String> getRemoteSymbolicReferences(String url, String pattern)
throws GitException, InterruptedException {
Map<String, String> references = new HashMap<>();
String regexPattern = null;
if (pattern != null) {
regexPattern = createRefRegexFromGlob(pattern);
}
// HACK ALERT... JGit doesn't give the info we require hard-coding HEAD symref support only based on pre 1.8.5
// behaviour of the git command line - whereby it would just look for the branch with the same ref as HEAD
// and if there are multiple matches and one of them is master then we can assume master... otherwise
// throw our hands up and say "no clue"
if (regexPattern != null && !Constants.HEAD.matches(regexPattern)) {
return references;
}
try (Repository repo = openDummyRepository()) {
LsRemoteCommand lsRemote = new LsRemoteCommand(repo);
lsRemote.setRemote(url);
lsRemote.setCredentialsProvider(getProvider());
Map<String, Ref> refs = lsRemote.callAsMap();
Ref target = refs.get(Constants.HEAD);
if (target == null) {
return references;
}
Set<String> candidates = new HashSet<>();
for (Map.Entry<String, Ref> entry: refs.entrySet()) {
if (entry.getValue() == target) {
continue;
}
if (entry.getValue().getObjectId().equals(target.getObjectId())) {
candidates.add(entry.getKey());
}
}
if (candidates.size() == 1) {
references.put(Constants.HEAD, candidates.iterator().next());
} else if (candidates.contains(Constants.R_HEADS+Constants.MASTER)) {
// if multiple heads have the same object ID, git 1.8.4 and earlier would give priority to master
references.put(Constants.HEAD, Constants.R_HEADS + Constants.MASTER);
} // else we have an inconclusive resolution
} catch (GitAPIException | 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)
{
Expand Down
Expand Up @@ -547,6 +547,12 @@ public Map<String, ObjectId> getRemoteReferences(String remoteRepoUrl, String pa
return proxy.getRemoteReferences(remoteRepoUrl, pattern, headsOnly, tagsOnly);
}

/** {@inheritDoc} */
public Map<String, String> getRemoteSymbolicReferences(String remoteRepoUrl, String pattern)
throws GitException, InterruptedException {
return proxy.getRemoteSymbolicReferences(remoteRepoUrl, pattern);
}

/** {@inheritDoc} */
public ObjectId revParse(String revName) throws GitException, InterruptedException {
return proxy.revParse(revName);
Expand Down
17 changes: 17 additions & 0 deletions src/test/java/org/jenkinsci/plugins/gitclient/GitAPITestCase.java
Expand Up @@ -3372,6 +3372,23 @@ public void test_getRemoteReferences_withMatchingPattern() throws Exception {
assertTrue(references.isEmpty());
}

/**
* Test getRemoteSymbolicReferences with listing all references
*/
public void test_getRemoteSymbolicReferences() throws Exception {
Map<String, String> references = w.git.getRemoteSymbolicReferences(remoteMirrorURL, null);
assertThat(references, hasEntry(is(Constants.HEAD), is(Constants.R_HEADS + Constants.MASTER)));
}

/**
* Test getRemoteSymbolicReferences with listing all references
*/
public void test_getRemoteSymbolicReferences_withMatchingPattern() throws Exception {
Map<String, String> references = w.git.getRemoteSymbolicReferences(remoteMirrorURL, Constants.HEAD);
assertThat(references, hasEntry(is(Constants.HEAD), is(Constants.R_HEADS + Constants.MASTER)));
assertThat(references.size(), is(1));
}

private Properties parseLsRemote(File file) throws IOException
{
Properties properties = new Properties();
Expand Down

0 comments on commit 64f4303

Please sign in to comment.