Skip to content

Commit

Permalink
Merge pull request #288 from MarkEWaite/add-getTags
Browse files Browse the repository at this point in the history
[JENKINS-45447] Add GitClient.getTags()
  • Loading branch information
MarkEWaite committed Dec 15, 2017
2 parents 5526a61 + b435102 commit 4dfc936
Show file tree
Hide file tree
Showing 12 changed files with 353 additions and 8 deletions.
8 changes: 7 additions & 1 deletion pom.xml
Expand Up @@ -11,7 +11,7 @@

<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>git-client</artifactId>
<version>2.6.1-SNAPSHOT</version>
<version>2.7.0-SNAPSHOT</version>
<packaging>hpi</packaging>

<name>Jenkins Git client plugin</name>
Expand Down Expand Up @@ -113,6 +113,12 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>nl.jqno.equalsverifier</groupId>
<artifactId>equalsverifier</artifactId>
<version>2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>git-server</artifactId>
Expand Down
43 changes: 40 additions & 3 deletions src/main/java/hudson/plugins/git/Branch.java
@@ -1,6 +1,6 @@
package hudson.plugins.git;

import org.eclipse.jgit.lib.Constants;
import java.util.Objects;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;

Expand Down Expand Up @@ -37,7 +37,44 @@ private static String strip(String name) {
* Returns branch name and SHA1 hash.
* @return branch name and SHA1 hash
*/
public @Override String toString() {
return "Branch " + name + "(" + sha1 + ")";
@Override
public String toString() {
return "Branch " + name + "(" + getSHA1String() + ")";
}

/**
* Returns a hash code value for the object. Considers sha1 and name in the
* calculation.
*
* @return a hash code value for this object.
*/
@Override
public int hashCode() {
return super.hashCode();
}

/**
* Indicates whether some other object is "equal to" this one. Includes sha1
* and name in the comparison. Objects of subclasses of this object are not
* equal to objects of this class, even if they add no fields.
*
* @param obj the reference object with which to compare.
* @return true if this object is the same as the obj argument; false
* otherwise
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Branch other = (Branch) obj;
return Objects.equals(this.name, other.name)
&& Objects.equals(this.sha1, other.sha1);
}
}
44 changes: 42 additions & 2 deletions src/main/java/hudson/plugins/git/GitObject.java
Expand Up @@ -5,6 +5,7 @@
import org.kohsuke.stapler.export.ExportedBean;

import java.io.Serializable;
import java.util.Objects;

/**
* An object in a git repository. Includes the SHA1 and name of the
Expand All @@ -15,8 +16,8 @@ public class GitObject implements Serializable {

private static final long serialVersionUID = 1L;

ObjectId sha1;
String name;
final ObjectId sha1;
final String name;

/**
* Constructor for GitObject, a named SHA1 (tag, branch, etc.).
Expand Down Expand Up @@ -57,4 +58,43 @@ public String getName() {
public String getSHA1String() {
return sha1 != null ? sha1.name() : null;
}

/**
* Returns a hash code value for the object. Considers sha1 and name in the
* calculation.
*
* @return a hash code value for this object.
*/
@Override
public int hashCode() {
int hash = 3;
hash = 97 * hash + Objects.hashCode(this.sha1);
hash = 97 * hash + Objects.hashCode(this.name);
return hash;
}

/**
* Indicates whether some other object is "equal to" this one. Includes sha1
* and name in the comparison. Objects of subclasses of this object are not
* equal to objects of this class, even if they add no fields.
*
* @param obj the reference object with which to compare.
* @return true if this object is the same as the obj argument; false
* otherwise
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final GitObject other = (GitObject) obj;
return Objects.equals(this.name, other.name)
&& Objects.equals(this.sha1, other.sha1);
}
}
37 changes: 37 additions & 0 deletions src/main/java/hudson/plugins/git/Tag.java
@@ -1,5 +1,6 @@
package hudson.plugins.git;

import java.util.Objects;
import org.eclipse.jgit.lib.ObjectId;

/**
Expand Down Expand Up @@ -57,4 +58,40 @@ public String getCommitSHA1() {
public void setCommitSHA1(String commitSHA1) {
this.commitSHA1 = commitSHA1;
}

/**
* Returns a hash code value for the object. Considers sha1 and name in the
* calculation.
*
* @return a hash code value for this object.
*/
@Override
public int hashCode() {
return super.hashCode();
}

/**
* Indicates whether some other object is "equal to" this one. Includes sha1
* and name in the comparison. Objects of subclasses of this object are not
* equal to objects of this class, even if they add no fields.
*
* @param obj the reference object with which to compare.
* @return true if this object is the same as the obj argument; false
* otherwise
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Tag other = (Tag) obj;
return Objects.equals(this.name, other.name)
&& Objects.equals(this.sha1, other.sha1);
}
}
53 changes: 53 additions & 0 deletions src/main/java/org/jenkinsci/plugins/gitclient/CliGitAPIImpl.java
Expand Up @@ -19,6 +19,7 @@
import hudson.plugins.git.Branch;
import hudson.plugins.git.GitException;
import hudson.plugins.git.GitLockFailedException;
import hudson.plugins.git.GitObject;
import hudson.plugins.git.IGitAPI;
import hudson.plugins.git.IndexEntry;
import hudson.plugins.git.Revision;
Expand Down Expand Up @@ -2995,4 +2996,56 @@ static private boolean setsidExists() {
}
return false;
}

/** {@inheritDoc} */
@Override
public Set<GitObject> getTags() throws GitException, InterruptedException {
ArgumentListBuilder args = new ArgumentListBuilder("show-ref", "--tags", "-d");
String result;
try {
result = launchCommandIn(args, workspace);
} catch (GitException ge) {
/* If no tags, then git show-ref --tags -d returns non-zero */
result = "";
}

/*
Output shows SHA1 and tag with (optional) marker for annotated tags
7ac27f7a051e1017da9f7c45ade8f091dbe6f99d refs/tags/git-3.6.4
7b5856ef2b4d35530a06d6482d0f4e972769d89b refs/tags/git-3.6.4^{}
*/
String[] output = result.split("[\\n\\r]+");
if (output.length == 0 || (output.length == 1 && output[0].isEmpty())) {
return Collections.EMPTY_SET;
}
Pattern pattern = Pattern.compile("(\\p{XDigit}{40})\\s+refs/tags/([^^]+)(\\^\\{\\})?");
Map<String, ObjectId> tagMap = new HashMap<>();
for (String line : output) {
Matcher matcher = pattern.matcher(line);
if (!matcher.find()) {
// Log the surprise and skip the line
String message = MessageFormat.format(
"git show-ref --tags -d output not matched in line: {0}",
line);
listener.getLogger().println(message);
continue;
}
String sha1String = matcher.group(1);
String tagName = matcher.group(2);
String trailingText = matcher.group(3);
boolean isPeeledRef = false;
if (trailingText != null && trailingText.equals("^{}")) { // Line ends with '^{}'
isPeeledRef = true;
}
/* Prefer peeled ref if available (for tag commit), otherwise take first tag reference seen */
if (isPeeledRef || !tagMap.containsKey(tagName)) {
tagMap.put(tagName, ObjectId.fromString(sha1String));
}
}
Set<GitObject> tags = new HashSet<>(tagMap.size());
for (Map.Entry<String, ObjectId> entry : tagMap.entrySet()) {
tags.add(new GitObject(entry.getKey(), entry.getValue()));
}
return tags;
}
}
9 changes: 9 additions & 0 deletions src/main/java/org/jenkinsci/plugins/gitclient/GitClient.java
Expand Up @@ -946,4 +946,13 @@ public interface GitClient {
* @throws java.lang.InterruptedException on thread interruption
*/
List<Branch> getBranchesContaining(String revspec, boolean allBranches) throws GitException, InterruptedException;

/**
* Return name and object ID of all tags in current repository.
*
* @return set of tags in current repository
* @throws hudson.plugins.git.GitException on Git exceptions
* @throws java.lang.InterruptedException on thread interruption
*/
Set<GitObject> getTags() throws GitException, InterruptedException;
}
30 changes: 29 additions & 1 deletion src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java
Expand Up @@ -127,9 +127,9 @@

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.plugins.git.GitObject;
import org.eclipse.jgit.api.RebaseCommand.Operation;
import org.eclipse.jgit.api.RebaseResult;
import org.eclipse.jgit.errors.MissingObjectException;

/**
* GitClient pure Java implementation using JGit.
Expand Down Expand Up @@ -2672,4 +2672,32 @@ private StoredConfig getConfig(String GIT_DIR) throws GitException {
throw new GitException(ioe);
}
}

/** {@inheritDoc} */
@Override
public Set<GitObject> getTags() throws GitException, InterruptedException {
Set<GitObject> peeledTags = new HashSet<>();
Set<String> tagNames = new HashSet<>();
try (Repository repo = getRepository()) {
Map<String, Ref> tagsRead = repo.getTags();
for (Map.Entry<String, Ref> entry : tagsRead.entrySet()) {
/* Prefer peeled ref if available (for tag commit), otherwise take first tag reference seen */
String tagName = entry.getKey();
Ref tagRef = entry.getValue();
if (!entry.getValue().isPeeled()) {
Ref peeledRef = repo.peel(tagRef);
if (peeledRef.getPeeledObjectId() != null) {
tagRef = peeledRef; // Use peeled ref instead of annotated ref
}
}
if (tagRef.isPeeled()) {
peeledTags.add(new GitObject(tagName, tagRef.getPeeledObjectId()));
} else if (!tagNames.contains(tagName)) {
peeledTags.add(new GitObject(tagName, tagRef.getObjectId()));
}
tagNames.add(entry.getKey());
}
}
return peeledTags;
}
}
Expand Up @@ -10,11 +10,11 @@
import hudson.model.TaskListener;
import hudson.plugins.git.Branch;
import hudson.plugins.git.GitException;
import hudson.plugins.git.GitObject;
import hudson.plugins.git.IGitAPI;
import hudson.plugins.git.IndexEntry;
import hudson.plugins.git.Revision;
import hudson.plugins.git.Tag;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.RemoteOutputStream;
import hudson.remoting.RemoteWriter;
Expand Down Expand Up @@ -880,4 +880,10 @@ public List<Branch> getBranchesContaining(String revspec, boolean allBranches)
throws GitException, InterruptedException {
return getGitAPI().getBranchesContaining(revspec, allBranches);
}

/** {@inheritDoc} */
@Override
public Set<GitObject> getTags() throws GitException, InterruptedException {
return proxy.getTags();
}
}
64 changes: 64 additions & 0 deletions src/test/java/hudson/plugins/git/BranchTest.java
@@ -0,0 +1,64 @@
package hudson.plugins.git;

import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.Ref;

import org.junit.Test;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;

import nl.jqno.equalsverifier.EqualsVerifier;

public class BranchTest {

private final String branchSHA1;
private final String branchName;
private final ObjectId branchHead;
private final Branch branch;
private final String refPrefix;
private final Ref branchRef;
private final Branch branchFromRef;

public BranchTest() {
this.branchSHA1 = "fa71f704f9b90fa1f857d1623f3fe33fa2277ca9";
this.branchName = "origin/master";
this.branchHead = ObjectId.fromString(branchSHA1);
this.refPrefix = "refs/remotes/";
this.branchRef = new ObjectIdRef.PeeledNonTag(Ref.Storage.NEW, refPrefix + branchName, branchHead);
this.branch = new Branch(branchName, branchHead);
this.branchFromRef = new Branch(branchRef);
}

@Test
public void testToString() {
assertThat(branch.toString(), is(branchFromRef.toString()));
}

@Test
public void testToString_Contents() {
String expected = "Branch " + branchName + "(" + branchSHA1 + ")";
assertThat(branch.toString(), is(expected));
}

@Test
public void hashCodeContract() {
assertThat(branch, is(branchFromRef));
assertThat(branch.hashCode(), is(branchFromRef.hashCode()));
}

@Test
public void constructorRefArgStripped() {
Ref ref = new ObjectIdRef.PeeledNonTag(Ref.Storage.LOOSE, refPrefix + branchName, branchHead);
Branch strippedBranch = new Branch(ref);
assertThat(strippedBranch.getName(), is(branchName));
}

@Test
public void equalsContract() {
EqualsVerifier.forClass(Branch.class)
.usingGetClass()
.withRedefinedSuperclass()
.verify();
}
}

0 comments on commit 4dfc936

Please sign in to comment.