Skip to content

Commit

Permalink
[JENKINS-65493] Allow excluding by topics (#789)
Browse files Browse the repository at this point in the history
* Allow exluding by topics

* Fix tests

* Fix format
  • Loading branch information
ingwarsw authored Aug 1, 2024
1 parent 5a78598 commit 97a75e0
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import hudson.util.ListBoxModel;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.net.MalformedURLException;
import java.time.Duration;
import java.time.format.DateTimeParseException;
Expand Down Expand Up @@ -99,13 +100,7 @@
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.github.GHMyself;
import org.kohsuke.github.GHOrganization;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GHRepositorySearchBuilder;
import org.kohsuke.github.GHUser;
import org.kohsuke.github.GitHub;
import org.kohsuke.github.HttpException;
import org.kohsuke.github.*;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
Expand Down Expand Up @@ -1038,16 +1033,9 @@ public void visitSources(SCMSourceObserver observer) throws IOException, Interru
"Skipping repository %s because it is archived",
repo.getName())));

} else if (!gitHubSCMNavigatorContext.getTopics().isEmpty()
&& !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) {
} else if (!topicMatches(gitHubSCMNavigatorContext, repo, listener.getLogger())) {
// exclude repositories which are missing one or more of the specified topics
witness.record(repo.getName(), false);
listener.getLogger()
.println(GitHubConsoleNote.create(
System.currentTimeMillis(),
String.format(
"Skipping repository %s because it is missing one or more of the following topics: '%s'",
repo.getName(), gitHubSCMNavigatorContext.getTopics())));
} else if (!repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePublicRepositories()) {
witness.record(repo.getName(), false);
listener.getLogger()
Expand Down Expand Up @@ -1127,17 +1115,9 @@ public void visitSources(SCMSourceObserver observer) throws IOException, Interru
System.currentTimeMillis(),
String.format(
"Skipping repository %s because it is archived", repo.getName())));
} else if (!gitHubSCMNavigatorContext.getTopics().isEmpty()
&& !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) {
} else if (!topicMatches(gitHubSCMNavigatorContext, repo, listener.getLogger())) {
// exclude repositories which are missing one or more of the specified topics
witness.record(repo.getName(), false);
listener.getLogger()
.println(GitHubConsoleNote.create(
System.currentTimeMillis(),
String.format(
"Skipping repository %s because it is missing one or more of the following topics: '%s'",
repo.getName(), gitHubSCMNavigatorContext.getTopics())));

} else if (!repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePublicRepositories()) {
witness.record(repo.getName(), false);
listener.getLogger()
Expand Down Expand Up @@ -1197,16 +1177,9 @@ public void visitSources(SCMSourceObserver observer) throws IOException, Interru
String.format(
"Skipping repository %s because it is archived", repo.getName())));

} else if (!gitHubSCMNavigatorContext.getTopics().isEmpty()
&& !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) {
} else if (!topicMatches(gitHubSCMNavigatorContext, repo, listener.getLogger())) {
// exclude repositories which are missing one or more of the specified topics
witness.record(repo.getName(), false);
listener.getLogger()
.println(GitHubConsoleNote.create(
System.currentTimeMillis(),
String.format(
"Skipping repository %s because it is missing one or more of the following topics: '%s'",
repo.getName(), gitHubSCMNavigatorContext.getTopics())));
} else if (gitHubSCMNavigatorContext.isExcludeForkedRepositories()
&& repo.getSource() != null) {
witness.record(repo.getName(), false);
Expand Down Expand Up @@ -1239,9 +1212,44 @@ public void visitSources(SCMSourceObserver observer) throws IOException, Interru
}
}

private boolean topicMatches(final GitHubSCMNavigatorContext context, final GHRepository repo, PrintStream logger)
throws IOException {
if (context.getTopics().isEmpty()) return true;

final List<String> topics = repo.listTopics();
return context.getTopics().stream().allMatch(topic -> {
if (topic.startsWith("-")) {
boolean contains = topics.contains(topic.substring(1));
if (contains) {
logger.println(GitHubConsoleNote.create(
System.currentTimeMillis(),
String.format(
"Skipping repository %s because it contains excluded topic: '%s'",
repo.getName(), topic)));
return false;
}
return true;
} else {
boolean contains = topics.contains(topic);
if (!contains) {
logger.println(GitHubConsoleNote.create(
System.currentTimeMillis(),
String.format(
"Skipping repository %s because it does not contain topic: '%s'",
repo.getName(), topic)));
return false;
}
return true;
}
});
}

private Iterable<GHRepository> searchRepositories(final GitHub github, final GitHubSCMNavigatorContext context) {
final GHRepositorySearchBuilder ghRepositorySearchBuilder = github.searchRepositories();
context.getTopics().forEach(ghRepositorySearchBuilder::topic);
context.getTopics().forEach(topic -> {
if (topic.startsWith("-")) ghRepositorySearchBuilder.q("-topic:" + topic.substring(1));
else ghRepositorySearchBuilder.topic(topic);
});
ghRepositorySearchBuilder.org(getRepoOwner());
if (!context.isExcludeForkedRepositories()) {
ghRepositorySearchBuilder.q("fork:true");
Expand Down Expand Up @@ -1330,16 +1338,9 @@ public void visitSource(String sourceName, SCMSourceObserver observer) throws IO
"Skipping repository %s because it is archived",
repo.getName())));

} else if (!gitHubSCMNavigatorContext.getTopics().isEmpty()
&& !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) {
} else if (!topicMatches(gitHubSCMNavigatorContext, repo, listener.getLogger())) {
// exclude repositories which are missing one or more of the specified topics
witness.record(repo.getName(), false);
listener.getLogger()
.println(GitHubConsoleNote.create(
System.currentTimeMillis(),
String.format(
"Skipping repository %s because it is missing one or more of the following topics: '%s'",
repo.getName(), gitHubSCMNavigatorContext.getTopics())));
} else if (!repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePublicRepositories()) {
witness.record(repo.getName(), false);
listener.getLogger()
Expand Down Expand Up @@ -1398,16 +1399,9 @@ public void visitSource(String sourceName, SCMSourceObserver observer) throws IO
String.format(
"Skipping repository %s because it is archived", repo.getName())));

} else if (!gitHubSCMNavigatorContext.getTopics().isEmpty()
&& !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) {
} else if (!topicMatches(gitHubSCMNavigatorContext, repo, listener.getLogger())) {
// exclude repositories which are missing one or more of the specified topics
witness.record(repo.getName(), false);
listener.getLogger()
.println(GitHubConsoleNote.create(
System.currentTimeMillis(),
String.format(
"Skipping repository %s because it is missing one or more of the following topics: '%s'",
repo.getName(), gitHubSCMNavigatorContext.getTopics())));
} else if (StringUtils.isNotBlank(gitHubSCMNavigatorContext.getTeamSlug())
&& !isRepositoryVisibleToTeam(org, repo, gitHubSCMNavigatorContext.getTeamSlug())) {
listener.getLogger()
Expand Down Expand Up @@ -1476,16 +1470,9 @@ public void visitSource(String sourceName, SCMSourceObserver observer) throws IO
String.format(
"Skipping repository %s because it is archived", repo.getName())));

} else if (!gitHubSCMNavigatorContext.getTopics().isEmpty()
&& !repo.listTopics().containsAll(gitHubSCMNavigatorContext.getTopics())) {
} else if (!topicMatches(gitHubSCMNavigatorContext, repo, listener.getLogger())) {
// exclude repositories which are missing one or more of the specified topics
witness.record(repo.getName(), false);
listener.getLogger()
.println(GitHubConsoleNote.create(
System.currentTimeMillis(),
String.format(
"Skipping repository %s because it is missing one or more of the following topics: '%s'",
repo.getName(), gitHubSCMNavigatorContext.getTopics())));
} else if (!repo.isPrivate() && gitHubSCMNavigatorContext.isExcludePublicRepositories()) {
witness.record(repo.getName(), false);
listener.getLogger()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<div>
<p>Specify a comma-separated list of <strong>topics</strong> to filter for repositories that have <u>all of them</u>.</p>
</div>
<p>Specify a comma-separated list of <strong>topics</strong> to filter for repositories that have (you can prefix topic with `-` to have it <u>excluded</u>) <u>all of them</u>.</p>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,32 @@ public void fetchRepos_BelongingToAuthenticatedUser_FilteredByMultipleTopics() t
assertEquals(projectNames, Collections.singleton("yolo-archived"));
}

@Test
public void fetchRepos_BelongingToAuthenticatedUser_ExcludeByTopic() throws Exception {
setCredentials(Collections.singletonList(credentials));
navigator = navigatorForRepoOwner("stephenc", credentials.getId());
navigator.setTraits(Collections.singletonList(new TopicsTrait("-awesome")));
final Set<String> projectNames = new HashSet<>();
final SCMSourceObserver observer = getObserver(projectNames);

navigator.visitSources(observer);

assertEquals(projectNames, Collections.singleton("yolo-archived"));
}

@Test
public void fetchRepos_BelongingToAuthenticatedUser_ExcludeAndFilterByTopic() throws Exception {
setCredentials(Collections.singletonList(credentials));
navigator = navigatorForRepoOwner("stephenc", credentials.getId());
navigator.setTraits(Collections.singletonList(new TopicsTrait("-awesome,octocat")));
final Set<String> projectNames = new HashSet<>();
final SCMSourceObserver observer = getObserver(projectNames);

navigator.visitSources(observer);

assertEquals(projectNames, Collections.singleton("yolo-archived"));
}

@Test
public void fetchOneRepo_BelongingToAuthenticatedUser_ExcludingArchived() throws Exception {
setCredentials(Collections.singletonList(credentials));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"request" : {
"urlPath" : "/search/repositories",
"method" : "GET",
"basicAuth": {
"username": "git-user",
"password": "git-secret"
},
"queryParameters" : {
"q" : {
"equalTo" : "-topic:awesome org:stephenc fork:true"
}
}
},
"response" : {
"status" : 200,
"bodyFileName" : "body-search-user-repos-O8W78.json",
"headers" : {
"Date" : "Fri, 31 Jan 2020 10:49:58 GMT",
"Content-Type" : "application/json; charset=utf-8",
"Server" : "GitHub.com",
"Status" : "200 OK",
"X-GitHub-Media-Type" : "github.v3; format=json",
"X-RateLimit-Limit" : "60",
"X-RateLimit-Remaining" : "54",
"X-RateLimit-Reset" : "1580469786",
"Access-Control-Expose-Headers" : "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin" : "*",
"Strict-Transport-Security" : "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options" : "deny",
"X-Content-Type-Options" : "nosniff",
"X-XSS-Protection" : "1; mode=block",
"Referrer-Policy" : "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy" : "default-src 'none'",
"X-GitHub-Request-Id" : "C1F9:C8CD:5FFC805:7325FB9:5E340656"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"request" : {
"urlPath" : "/search/repositories",
"method" : "GET",
"basicAuth": {
"username": "git-user",
"password": "git-secret"
},
"queryParameters" : {
"q" : {
"equalTo" : "-topic:awesome topic:octocat org:stephenc fork:true"
}
}
},
"response" : {
"status" : 200,
"bodyFileName" : "body-search-user-repos-O8W78.json",
"headers" : {
"Date" : "Fri, 31 Jan 2020 10:49:58 GMT",
"Content-Type" : "application/json; charset=utf-8",
"Server" : "GitHub.com",
"Status" : "200 OK",
"X-GitHub-Media-Type" : "github.v3; format=json",
"X-RateLimit-Limit" : "60",
"X-RateLimit-Remaining" : "54",
"X-RateLimit-Reset" : "1580469786",
"Access-Control-Expose-Headers" : "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type",
"Access-Control-Allow-Origin" : "*",
"Strict-Transport-Security" : "max-age=31536000; includeSubdomains; preload",
"X-Frame-Options" : "deny",
"X-Content-Type-Options" : "nosniff",
"X-XSS-Protection" : "1; mode=block",
"Referrer-Policy" : "origin-when-cross-origin, strict-origin-when-cross-origin",
"Content-Security-Policy" : "default-src 'none'",
"X-GitHub-Request-Id" : "C1F9:C8CD:5FFC805:7325FB9:5E340656"
}
}
}

0 comments on commit 97a75e0

Please sign in to comment.