Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gitea release handling #60

Merged
merged 17 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"cSpell.words": [
"gitea",
"repos"
]
],
"java.configuration.updateBuildConfiguration": "interactive"
}
9 changes: 4 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>scm-api</artifactId>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>branch-api</artifactId>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>git</artifactId>
Expand All @@ -100,11 +104,6 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>branch-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-multibranch</artifactId>
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/org/jenkinsci/plugin/gitea/GiteaNotifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,111 +132,120 @@
status.getState().name(), status.getDescription());
hash = ((TagSCMRevision) revision).getHash();
statusContext += "tag";
} else if (revision instanceof ReleaseSCMRevision) {
listener.getLogger().format("[Gitea] Notifying release build status: %s %s%n",
status.getState().name(), status.getDescription());
hash = ((ReleaseSCMRevision) revision).getHash();
statusContext += "release";
} else {
return;
}
status.setContext(statusContext);
JobScheduledListener jsl = ExtensionList.lookup(QueueListener.class).get(JobScheduledListener.class);
if (jsl != null) {
// we are setting the status, so don't let the queue listener background thread change it to pending
synchronized (jsl.resolving) {
jsl.resolving.remove(build.getParent());
}
}
try (GiteaConnection c = source.gitea().open()) {
int tries = 3;
while (true){
tries--;
try {
c.createCommitStatus(source.getRepoOwner(), source.getRepository(), hash, status);
break;
} catch (GiteaHttpStatusException e) {
if (e.getStatusCode() == 500 && tries > 0) {
// server may be overloaded
continue;
}
throw e;
}
}
listener.getLogger().format("[Gitea] Notified%n");
}
}

/**
* Strips the name of the job the remove either the name of the branch or e.g. PR-1
* @param job Job with a full name in form of Organisation/Repository/Branch
* @return Stripped name in form of Organisation/Repository
*/
private static String stripBranchName(Job job) {
return job.getFullName().substring(0, job.getFullName().lastIndexOf('/'));
}

private static String getPrContextTarget(String targetbranch) {
return "pr-" + targetbranch;
}

/**
* Listener to catch jobs being added to the build queue.
*/
@Extension
public static class JobScheduledListener extends QueueListener {
/**
* Track requests so that we can know if there is a newer request.
*/
private final AtomicLong nonce = new AtomicLong();
/**
* Active resolution of likely revisions for queued jobs.
*/
private final Map<Job, Long> resolving = new HashMap<>();

/**
* {@inheritDoc}
*/
@Override
public void onEnterWaiting(final Queue.WaitingItem wi) {
if (!(wi.task instanceof Job)) {
return;
}
final Job<?, ?> job = (Job) wi.task;
final SCMSource src = SCMSource.SourceByItem.findSource(job);
if (!(src instanceof GiteaSCMSource)) {
return;
}
final GiteaSCMSource source = (GiteaSCMSource) src;
if (new GiteaSCMSourceContext(null, SCMHeadObserver.none())
.withTraits(source.getTraits())
.notificationsDisabled()) {
return;
}
final SCMHead head = SCMHead.HeadByItem.findHead(job);
if (head == null) {
return;
}
final Long nonce = this.nonce.incrementAndGet();
synchronized (resolving) {
resolving.put(job, nonce);
}
// prevent delays in the queue when updating Gitea
Computer.threadPoolForRemoting.submit(new Runnable() {
@Override
public void run() {
try(ACLContext context = ACL.as(Tasks.getAuthenticationOf(wi.task))) {
// we need to determine the revision that *should* be built so that we can tag that as pending
SCMRevision revision = source.fetch(head, new LogTaskListener(LOGGER, Level.INFO));
String hash;
String statusContext = stripBranchName(job) + "/pipeline/";
if (revision instanceof BranchSCMRevision) {
LOGGER.log(Level.INFO, "Notifying branch pending build {0}", job.getFullName());
hash = ((BranchSCMRevision) revision).getHash();
statusContext += "head";
} else if (revision instanceof PullRequestSCMRevision) {
LOGGER.log(Level.INFO, "Notifying pull request pending build {0}", job.getFullName());
hash = ((PullRequestSCMRevision) revision).getOrigin().getHash();
statusContext += getPrContextTarget(((PullRequestSCMRevision) revision).getTarget().getHead().getName());
} else if (revision instanceof TagSCMRevision) {
LOGGER.log(Level.INFO, "Notifying tag pending build {0}", job.getFullName());
statusContext += "tag";
hash = ((TagSCMRevision) revision).getHash();
} else if (revision instanceof ReleaseSCMRevision) {
LOGGER.log(Level.INFO, "Notifying release pending build {0}", job.getFullName());
statusContext += "release";
hash = ((ReleaseSCMRevision) revision).getHash();

Check warning on line 248 in src/main/java/org/jenkinsci/plugin/gitea/GiteaNotifier.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 135-248 are not covered by tests
} else {
return;
}
Expand Down
91 changes: 91 additions & 0 deletions src/main/java/org/jenkinsci/plugin/gitea/GiteaReleaseNotifier.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* The MIT License
*
* Copyright (c) 2017-2022, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.plugin.gitea;

import hudson.Extension;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.listeners.RunListener;
import java.io.IOException;
import jenkins.scm.api.SCMHead;
import jenkins.scm.api.SCMHeadObserver;
import jenkins.scm.api.SCMSource;
import jenkins.util.VirtualFile;
import org.jenkinsci.plugin.gitea.client.api.GiteaConnection;
import org.jenkinsci.plugin.gitea.client.api.GiteaRepository;

public class GiteaReleaseNotifier {

public static void publishArtifacts(Run<?, ?> build, TaskListener listener)
throws IOException, InterruptedException {

if (build.getResult() != Result.SUCCESS) {
// do not push assets when the pipeline wasn't a success
listener.getLogger().format("[Gitea] do not publish assets due to build being non-Successfully%n");
return;
}

final SCMSource s = SCMSource.SourceByItem.findSource(build.getParent());
if (!(s instanceof GiteaSCMSource)) {
listener.getLogger().format("[Gitea] do not publish assets due to source being no GiteaSCMSource%n");
return;
}
final GiteaSCMSource source = (GiteaSCMSource) s;
if (!new GiteaSCMSourceContext(null, SCMHeadObserver.none())
.withTraits(source.getTraits())
.artifactToAssetMappingEnabled()) {
return;
}
final SCMHead head = SCMHead.HeadByItem.findHead(build.getParent());
if (head == null || !(head instanceof ReleaseSCMHead)) {
listener.getLogger().format("[Gitea] do not publish assets due to head either being null or no ReleaseSCMHead%n");
return;
}

try (GiteaConnection c = source.gitea().open()) {
GiteaRepository repository = c.fetchRepository(source.getRepoOwner(), source.getRepository());
long releaseId = ((ReleaseSCMHead) head).getId();

for (Run<?, ?>.Artifact artifact : build.getArtifacts()) {
VirtualFile file = build.getArtifactManager().root().child(artifact.relativePath);
c.createReleaseAttachment(repository, releaseId, artifact.getFileName(), file.open());
listener.getLogger().format("[Gitea] Published asset from archived artifact %s%n", artifact.getFileName());
}
}
}

@Extension
public static class JobCompletedListener extends RunListener<Run<?, ?>> {

@Override
public void onCompleted(Run<?, ?> build, TaskListener listener) {
try {
publishArtifacts(build, listener);
} catch (IOException | InterruptedException e) {
e.printStackTrace(listener.error("Could not upload assets for release"));
}
}

Check warning on line 89 in src/main/java/org/jenkinsci/plugin/gitea/GiteaReleaseNotifier.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 39-89 are not covered by tests
}
}
80 changes: 80 additions & 0 deletions src/main/java/org/jenkinsci/plugin/gitea/GiteaReleaseSCMEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* The MIT License
*
* Copyright (c) 2017-2022, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.plugin.gitea;

import hudson.Extension;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.logging.Logger;
import jenkins.scm.api.SCMHead;
import jenkins.scm.api.SCMHeadEvent;
import jenkins.scm.api.SCMRevision;
import org.jenkinsci.plugin.gitea.client.api.GiteaConnection;
import org.jenkinsci.plugin.gitea.client.api.GiteaReleaseEvent;
import org.jenkinsci.plugin.gitea.client.api.GiteaTag;

public class GiteaReleaseSCMEvent extends AbstractGiteaSCMHeadEvent<GiteaReleaseEvent> {

public static final Logger LOGGER = Logger.getLogger(GiteaReleaseSCMEvent.class.getName());

public GiteaReleaseSCMEvent(GiteaReleaseEvent payload, String origin) {
super(Type.CREATED, payload, origin);
}

@Override
protected Map<SCMHead, SCMRevision> headsFor(GiteaSCMSource source) {
if (getPayload().getRelease().isDraft()) {
// skip draft releases
return Collections.<SCMHead, SCMRevision>emptyMap();
}

String ref = getPayload().getRelease().getTagName();
String sha = null;

try (GiteaConnection c = source.gitea().open()) {
GiteaTag releaseTag = c.fetchTag(source.getRepoOwner(), source.getRepository(), ref);
sha = releaseTag.getCommit().getSha();
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}

ReleaseSCMHead h = new ReleaseSCMHead(ref, getPayload().getRelease().getId());
return Collections.<SCMHead, SCMRevision>singletonMap(h, new ReleaseSCMRevision(h, sha));
}

@Extension
public static class HandlerImpl extends GiteaWebhookHandler<GiteaReleaseSCMEvent, GiteaReleaseEvent> {

@Override
protected GiteaReleaseSCMEvent createEvent(GiteaReleaseEvent payload, String origin) {
return new GiteaReleaseSCMEvent(payload, origin);
}

@Override
protected void process(GiteaReleaseSCMEvent event) {
SCMHeadEvent.fireNow(event);
}

Check warning on line 78 in src/main/java/org/jenkinsci/plugin/gitea/GiteaReleaseSCMEvent.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 40-78 are not covered by tests
}
}
3 changes: 3 additions & 0 deletions src/main/java/org/jenkinsci/plugin/gitea/GiteaSCMBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@
} else if (head instanceof TagSCMHead) {
withRefSpec("+refs/tags/" + head.getName() + ":refs/tags/@{remote}/" + head.getName());
repoUrl = repositoryUrl(repoOwner, repository);
} else if (head instanceof ReleaseSCMHead) {
withRefSpec("+refs/tags/" + head.getName() + ":refs/tags/@{remote}/" + head.getName());
Mai-Lapyst marked this conversation as resolved.
Show resolved Hide resolved
repoUrl = repositoryUrl(repoOwner, repository);

Check warning on line 114 in src/main/java/org/jenkinsci/plugin/gitea/GiteaSCMBuilder.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 112-114 are not covered by tests
} else {
withRefSpec("+refs/heads/" + head.getName() + ":refs/remotes/@{remote}/" + head.getName());
repoUrl = repositoryUrl(repoOwner, repository);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,80 +68,86 @@
this.ref = ((BranchSCMRevision) rev).getHash();
} else if (rev instanceof TagSCMRevision) {
this.ref = ((TagSCMRevision) rev).getHash();
} else if (rev instanceof ReleaseSCMRevision) {
this.ref = ((ReleaseSCMRevision) rev).getHash();
} else {
this.ref = ref;
}
} else {
this.ref = ref;
}
}

@Override
public void close() throws IOException {
connection.close();
}

@Override
public long lastModified() throws IOException {
// TODO once https://github.com/go-gitea/gitea/issues/1978
return 0L;
}

@NonNull
@Override
public SCMFile getRoot() {
return new GiteaSCMFile(connection, repo, ref);
}

@Extension
public static class BuilderImpl extends Builder {

@Override
public boolean supports(SCM source) {
// TODO implement a GiteaSCM so we can work for those
return false;
}

@Override
public boolean supports(SCMSource source) {
return source instanceof GiteaSCMSource;
}

@Override
protected boolean supportsDescriptor(SCMDescriptor scmDescriptor) {
// TODO
return false;
}

@Override
protected boolean supportsDescriptor(SCMSourceDescriptor scmSourceDescriptor) {
return scmSourceDescriptor instanceof GiteaSCMSource.DescriptorImpl;
}

@Override
public SCMFileSystem build(@NonNull Item owner, @NonNull SCM scm, @CheckForNull SCMRevision rev) {
return null;
}

@Override
public SCMFileSystem build(@NonNull SCMSource source, @NonNull SCMHead head, @CheckForNull SCMRevision rev)
throws IOException, InterruptedException {
GiteaSCMSource src = (GiteaSCMSource) source;
String repoOwner;
String repository;
String ref;
if (head instanceof PullRequestSCMHead) {
repoOwner = src.getRepoOwner();
repository = src.getRepository();
ref = head.getName();
} else if (head instanceof BranchSCMHead) {
repoOwner = src.getRepoOwner();
repository = src.getRepository();
ref = head.getName();
} else if (head instanceof TagSCMHead) {
repoOwner = src.getRepoOwner();
repository = src.getRepository();
ref = head.getName();
} else if (head instanceof ReleaseSCMHead) {
repoOwner = src.getRepoOwner();
repository = src.getRepository();
ref = head.getName();

Check warning on line 150 in src/main/java/org/jenkinsci/plugin/gitea/GiteaSCMFileSystem.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 71-150 are not covered by tests
} else {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
private final String serverUrl;
private final String repoOwner;
private String credentialsId;
private List<SCMTrait<?>> traits = new ArrayList<>();
private List<SCMTrait<? extends SCMTrait<?>>> traits = new ArrayList<>();

Check warning on line 94 in src/main/java/org/jenkinsci/plugin/gitea/GiteaSCMNavigator.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 94 is not covered by tests
private GiteaOwner giteaOwner;

@DataBoundConstructor
Expand All @@ -118,7 +118,7 @@
}

@NonNull
public List<SCMTrait<?>> getTraits() {
public List<SCMTrait<? extends SCMTrait<?>>> getTraits() {
return Collections.unmodifiableList(traits);
}

Expand Down
59 changes: 57 additions & 2 deletions src/main/java/org/jenkinsci/plugin/gitea/GiteaSCMSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
import org.jenkinsci.plugin.gitea.client.api.GiteaIssueState;
import org.jenkinsci.plugin.gitea.client.api.GiteaOwner;
import org.jenkinsci.plugin.gitea.client.api.GiteaPullRequest;
import org.jenkinsci.plugin.gitea.client.api.GiteaRelease;
import org.jenkinsci.plugin.gitea.client.api.GiteaRepository;
import org.jenkinsci.plugin.gitea.client.api.GiteaTag;
import org.jenkinsci.plugin.gitea.client.api.GiteaVersion;
Expand Down Expand Up @@ -261,719 +262,773 @@
listener.getLogger().format("Pull request #%s is CLOSED%n", h.getId());
return null;
}
} else if (head instanceof ReleaseSCMHead) {
ReleaseSCMHead h = (ReleaseSCMHead) head;
final GiteaTag tag = c.fetchTag(repoOwner, repository, h.getName());
String revision = tag.getCommit().getSha();
return new ReleaseSCMRevision(new ReleaseSCMHead(h.getName(), h.getId()), revision);
} else {
listener.getLogger().format("Unknown head: %s of type %s%n", head.getName(), head.getClass().getName());
return null;
}
}
}

@Override
protected void retrieve(SCMSourceCriteria criteria, @NonNull SCMHeadObserver observer, SCMHeadEvent<?> event,
@NonNull final TaskListener listener) throws IOException, InterruptedException {
try (GiteaConnection c = gitea().open()) {
listener.getLogger().format("Looking up repository %s/%s%n", repoOwner, repository);
giteaRepository = c.fetchRepository(repoOwner, repository);
sshRemote = giteaRepository.getSshUrl();
if (giteaRepository.isEmpty()) {
listener.getLogger().format("Repository %s is empty%n", repository);
return;
}
try (GiteaSCMSourceRequest request = new GiteaSCMSourceContext(criteria, observer)
.withTraits(getTraits())
.newRequest(this, listener)) {
request.setConnection(c);
if (request.isFetchBranches()) {
request.setBranches(c.fetchBranches(giteaRepository));
}
if (request.isFetchPRs()) {
if (giteaRepository.isMirror()) {
listener.getLogger().format("%n Ignoring pull requests as repository is a mirror...%n");
} else {
request.setPullRequests(c.fetchPullRequests(giteaRepository, EnumSet.of(GiteaIssueState.OPEN)));
}
}
if (request.isFetchTags()) {
final GiteaVersion version = c.fetchVersion();
VersionNumber v = version.getVersionNumber();
if (v.isOlderThan(TAG_SUPPORT_MINIMUM_VERSION)) {
listener.getLogger()
.format("%n Ignoring tags as Gitea server is version %s and version %s is the "
+ "minimum version to support tag indexing%n",
version.getVersion(), TAG_SUPPORT_MINIMUM_VERSION.toString());
request.setTags(null);
} else {
request.setTags(c.fetchTags(giteaRepository));
}
}
if (request.isFetchReleases()) {
request.setReleases(c.fetchReleases(giteaRepository, false, request.isIncludingPreReleases()));
}

if (request.isFetchBranches()) {
int count = 0;
listener.getLogger().format("%n Checking branches...%n");
for (final GiteaBranch b : request.getBranches()) {
count++;
listener.getLogger().format("%n Checking branch %s%n",
HyperlinkNote.encodeTo(
UriTemplate.buildFromTemplate(giteaRepository.getHtmlUrl())
.literal("/src/branch")
.path("branch")
.build()
.set("branch", b.getName())
.expand(),
b.getName()
)
);
final BranchSCMHead head = new BranchSCMHead(b.getName());
final BranchSCMRevision revision = new BranchSCMRevision(head, b.getCommit().getId());
if (request.process(head, revision, this::createProbe, new CriteriaWitness<>(listener))) {
listener.getLogger().format("%n %d branches were processed (query completed)%n", count);
return;
}
}
listener.getLogger().format("%n %d branches were processed%n", count);
}
if (request.isFetchPRs() && !giteaRepository.isMirror() && !(request.getForkPRStrategies().isEmpty()
&& request.getOriginPRStrategies().isEmpty())) {
int count = 0;
listener.getLogger().format("%n Checking pull requests...%n");
for (final GiteaPullRequest p : request.getPullRequests()) {
if (p == null) {
continue;
}
count++;
listener.getLogger().format("%n Checking pull request %s%n",
HyperlinkNote.encodeTo(
UriTemplate.buildFromTemplate(giteaRepository.getHtmlUrl())
.literal("/pulls")
.path("number")
.build()
.set("number", p.getNumber())
.expand(),
"#" + p.getNumber()
)
);
String originOwner = p.getHead().getRepo().getOwner().getUsername();
String originRepository = p.getHead().getRepo().getName();
Set<ChangeRequestCheckoutStrategy> strategies = request.getPRStrategies(
!StringUtils.equalsIgnoreCase(repoOwner, originOwner)
&& StringUtils.equalsIgnoreCase(repository, originRepository)
);
for (ChangeRequestCheckoutStrategy strategy : strategies) {
final PullRequestSCMHead head = new PullRequestSCMHead(
"PR-" + p.getNumber() + (strategies.size() > 1 ? "-" + strategy.name()
.toLowerCase(Locale.ENGLISH) : ""),
p.getNumber(),
new BranchSCMHead(p.getBase().getRef()),
strategy,
StringUtils.equalsIgnoreCase(originOwner, repoOwner)
&& StringUtils.equalsIgnoreCase(originRepository, repository)
? SCMHeadOrigin.DEFAULT
: new SCMHeadOrigin.Fork(originOwner + "/" + originRepository),
originOwner,
originRepository,
p.getHead().getRef());
PullRequestSCMRevision revision = new PullRequestSCMRevision(
head,
new BranchSCMRevision(
head.getTarget(),
p.getBase().getSha()
),
new BranchSCMRevision(
new BranchSCMHead(head.getOriginName()),
p.getHead().getSha()
)
);
if (request.process(head, revision, this::createProbe, new CriteriaWitness<>(listener)
)) {
listener.getLogger()
.format("%n %d pull requests were processed (query completed)%n", count);
return;
}

}
}
listener.getLogger().format("%n %d pull requests were processed%n", count);
}
if (request.isFetchTags()) {
int count = 0;
listener.getLogger().format("%n Checking tags...%n");
for (final GiteaTag tag : request.getTags()) {
if (tag.getCommit() == null) {
// bad data from server, ignore
continue;
}
count++;
listener.getLogger().format("%n Checking tag %s%n",
HyperlinkNote.encodeTo(
UriTemplate.buildFromTemplate(giteaRepository.getHtmlUrl())
.literal("/src/tag")
.path("tag")
.build()
.set("tag", tag.getName())
.expand(),
tag.getName()
)
);
Date timestamp = null;
if (!tag.getId().equalsIgnoreCase(tag.getCommit().getSha())) {
// annotated tag, timestamp is annotation time
try {
GiteaAnnotatedTag annotatedTag =
c.fetchAnnotatedTag(repoOwner, repository, tag.getId());
listener.getLogger().format("annotated tag: %s%n", annotatedTag);
GiteaAnnotatedTag.Tagger tagger = annotatedTag.getTagger();
timestamp = tagger != null ? tagger.getDate() : null;
} catch (GiteaHttpStatusException e) {
// ignore, best effort, fall back to commit
}
}
if (timestamp == null) {
// try to get the timestamp of the commit itself
try {
GiteaCommitDetail detail =
c.fetchCommit(repoOwner, repository, tag.getCommit().getSha());
GiteaCommitDetail.GitCommit commit = detail.getCommit();
GiteaCommitDetail.GitActor committer = commit != null ? commit.getCommitter() : null;
timestamp = committer != null ? committer.getDate() : null;
} catch (GiteaHttpStatusException e) {
if (e.getStatusCode() != 404) {
throw e;
}
}
}
TagSCMHead head = new TagSCMHead(tag.getName(), timestamp == null ? 0L : timestamp.getTime());
TagSCMRevision revision = new TagSCMRevision(head, tag.getCommit().getSha());
if (request.process(head, revision, this::createProbe, new CriteriaWitness<>(listener))) {
listener.getLogger().format("%n %d tags were processed (query completed)%n", count);
return;
}
}
listener.getLogger().format("%n %d tags were processed%n", count);
}
if (request.isFetchReleases()) {
int count = 0;
listener.getLogger().format("%n Checking releases...%n");
for (final GiteaRelease release : request.getReleases()) {
count++;
Mai-Lapyst marked this conversation as resolved.
Show resolved Hide resolved
listener.getLogger().format("%n Checking release '%s' (tag %s)%n",
release.getName(),
HyperlinkNote.encodeTo(
UriTemplate.buildFromTemplate(giteaRepository.getHtmlUrl())
.literal("/src/tag")
.path("tag")
.build()
.set("tag", release.getTagName())
.expand(),
release.getTagName()
)
);
final GiteaTag releaseTag = c.fetchTag(giteaRepository, release.getTagName());
final ReleaseSCMHead head = new ReleaseSCMHead(release.getTagName(), release.getId());
final ReleaseSCMRevision revision = new ReleaseSCMRevision(head, releaseTag.getCommit().getSha());
if (request.process(head, revision, this::createProbe, new CriteriaWitness<>(listener))) {
listener.getLogger().format("%n %d releases were processed (query completed)%n", count);
return;
}
}
listener.getLogger().format("%n %d releases were processed%n", count);
}
}
}
}

@NonNull
@Override
protected List<Action> retrieveActions(SCMSourceEvent event, @NonNull TaskListener listener)
throws IOException, InterruptedException {
if (giteaRepository == null) {
try (GiteaConnection c = gitea().open()) {
listener.getLogger().format("Looking up repository %s/%s%n", repoOwner, repository);
giteaRepository = c.fetchRepository(repoOwner, repository);
}
}
List<Action> result = new ArrayList<>();
result.add(new ObjectMetadataAction(giteaRepository.getName(), giteaRepository.getDescription(), giteaRepository.getWebsite()));
if (StringUtils.isNotBlank(giteaRepository.getAvatarUrl())) {
result.add(new GiteaAvatar(giteaRepository.getAvatarUrl()));
}
result.add(new GiteaLink("icon-gitea-repo", UriTemplate.buildFromTemplate(serverUrl)
.path(UriTemplateBuilder.var("owner"))
.path(UriTemplateBuilder.var("repository"))
.build()
.set("owner", repoOwner)
.set("repository", repository)
.expand()
));
return result;
}

@NonNull
@Override
protected List<Action> retrieveActions(@NonNull SCMHead head, SCMHeadEvent event, @NonNull TaskListener listener)
throws IOException, InterruptedException {
if (giteaRepository == null) {
try (GiteaConnection c = gitea().open()) {
listener.getLogger().format("Looking up repository %s/%s%n", repoOwner, repository);
giteaRepository = c.fetchRepository(repoOwner, repository);
}
}
List<Action> result = new ArrayList<>();
if (head instanceof BranchSCMHead) {
String branchUrl = UriTemplate.buildFromTemplate(serverUrl)
.path(UriTemplateBuilder.var("owner"))
.path(UriTemplateBuilder.var("repository"))
.literal("/src/branch")
.path(UriTemplateBuilder.var("branch"))
.build()
.set("owner", repoOwner)
.set("repository", repository)
.set("branch", head.getName())
.expand();
result.add(new ObjectMetadataAction(
null,
null,
branchUrl
));
result.add(new GiteaLink("icon-gitea-branch", branchUrl));
if (head.getName().equals(giteaRepository.getDefaultBranch())) {
result.add(new PrimaryInstanceMetadataAction());
}
} else if (head instanceof TagSCMHead) {
String tagUrl = UriTemplate.buildFromTemplate(serverUrl)
.path(UriTemplateBuilder.var("owner"))
.path(UriTemplateBuilder.var("repository"))
.literal("/src/tag")
.path(UriTemplateBuilder.var("tag"))
.build()
.set("owner", repoOwner)
.set("repository", repository)
.set("tag", head.getName())
.expand();
result.add(new ObjectMetadataAction(
null,
null,
tagUrl
));
result.add(new GiteaLink("icon-gitea-branch", tagUrl));
if (head.getName().equals(giteaRepository.getDefaultBranch())) {
result.add(new PrimaryInstanceMetadataAction());
}
} else if (head instanceof PullRequestSCMHead) {
String pullUrl = UriTemplate.buildFromTemplate(serverUrl)
.path(UriTemplateBuilder.var("owner"))
.path(UriTemplateBuilder.var("repository"))
.literal("/pulls")
.path(UriTemplateBuilder.var("id"))
.build()
.set("owner", repoOwner)
.set("repository", repository)
.set("id", ((PullRequestSCMHead) head).getId())
.expand();
result.add(new ObjectMetadataAction(
null,
null,
pullUrl
));
result.add(new GiteaLink("icon-gitea-branch", pullUrl));
} else if (head instanceof ReleaseSCMHead) {
String releaseUrl = UriTemplate.buildFromTemplate(serverUrl)
.path(UriTemplateBuilder.var("owner"))
.path(UriTemplateBuilder.var("repository"))
.literal("/releases")
.literal("/tag")
.path(UriTemplateBuilder.var("name"))
.build()
.set("owner", repoOwner)
.set("repository", repository)
.set("name", head.getName())
.expand();
result.add(new ObjectMetadataAction(
null,
null,
releaseUrl
));
result.add(new GiteaLink("icon-gitea-logo", releaseUrl));
}
return result;
}

@NonNull
@Override
protected List<Action> retrieveActions(@NonNull SCMRevision revision, SCMHeadEvent event,
@NonNull TaskListener listener) throws IOException, InterruptedException {
return super.retrieveActions(revision, event, listener);
}

@NonNull
@Override
public SCMRevision getTrustedRevision(@NonNull SCMRevision revision, @NonNull TaskListener listener)
throws IOException, InterruptedException {
if (revision instanceof PullRequestSCMRevision) {
PullRequestSCMHead head = (PullRequestSCMHead) revision.getHead();
try (GiteaConnection c = gitea().open()) {
try (GiteaSCMSourceRequest request = new GiteaSCMSourceContext(null, SCMHeadObserver.none())
.withTraits(getTraits())
.newRequest(this, listener)) {
request.setConnection(c);

final GiteaVersion giteaVersion = c.fetchVersion();
final VersionNumber versionNumber = giteaVersion.getVersionNumber();

if (!versionNumber.isOlderThan(READ_ACCESS_COLLABORATOR_LISTING_SUPPORT_MINIMUM_VERSION) ||
giteaRepository.getPermissions().isAdmin()) {
request.setCollaboratorNames(
c.fetchCollaborators(giteaRepository).stream().map(GiteaOwner::getUsername)
.collect(Collectors.toSet()));
} else {
listener.getLogger()
.format("%n[Gitea] Ignore collaborator fetching because Gitea server version is %s " +
"and it's requires admin privileges. From %s, " +
"collaborator fetching requires only read permission to repository.%n",
giteaVersion.getVersion(),
READ_ACCESS_COLLABORATOR_LISTING_SUPPORT_MINIMUM_VERSION.toString());
}

if (request.isTrusted(head)) {
return revision;
}
}
PullRequestSCMRevision rev = (PullRequestSCMRevision) revision;
listener.getLogger().format("Loading trusted files from base branch %s at %s rather than %s%n",
head.getTarget().getName(), ((SCMRevisionImpl) rev.getTarget()).getHash(),
rev.getOrigin().getHash());
return rev.getTarget();
}
}
return revision;
}

@NonNull
@Override
public SCM build(@NonNull SCMHead head, SCMRevision revision) {
return new GiteaSCMBuilder(this, head, revision).withTraits(traits).build();
}

@NonNull
@Override
protected SCMProbe createProbe(@NonNull final SCMHead head, SCMRevision revision) throws IOException {
try {
GiteaSCMFileSystem.BuilderImpl builder =
ExtensionList.lookup(SCMFileSystem.Builder.class).get(GiteaSCMFileSystem.BuilderImpl.class);
if (builder == null) {
throw new AssertionError();
}
final SCMFileSystem fs = builder.build(this, head, revision);
return new SCMProbe() {
@NonNull
@Override
public SCMProbeStat stat(@NonNull String path) throws IOException {
try {
return SCMProbeStat.fromType(fs.child(path).getType());
} catch (InterruptedException e) {
throw new IOException("Interrupted", e);
}
}

@Override
public void close() throws IOException {
fs.close();
}

@Override
public String name() {
return head.getName();
}

@Override
public long lastModified() {
try {
return fs.lastModified();
} catch (IOException | InterruptedException e) {
return 0L;
}
}

@Override
public SCMFile getRoot() {
return fs.getRoot();
}
};
} catch (InterruptedException e) {
throw new IOException(e);
}
}

@Override
public void afterSave() {
WebhookRegistration mode = new GiteaSCMSourceContext(null, SCMHeadObserver.none())
.withTraits(new GiteaSCMNavigatorContext().withTraits(traits).traits())
.webhookRegistration();
GiteaWebhookListener.register(getOwner(), this, mode, credentialsId);
}

/*package*/ Gitea gitea() throws AbortException {
public Gitea gitea() throws AbortException {
GiteaServer server = GiteaServers.get().findServer(serverUrl);
if (server == null) {
throw new AbortException("Unknown server: " + serverUrl);
}
StandardCredentials credentials = credentials();
SCMSourceOwner owner = getOwner();
if (owner != null) {
CredentialsProvider.track(owner, credentials);
}
return Gitea.server(serverUrl)
.as(AuthenticationTokens.convert(GiteaAuth.class, credentials));
}

public StandardCredentials credentials() {
SCMSourceOwner owner = getOwner();
return CredentialsMatchers.firstOrNull(
CredentialsProvider.lookupCredentials(
StandardCredentials.class,
owner,
owner instanceof Queue.Task ?
((Queue.Task) owner).getDefaultAuthentication()
: ACL.SYSTEM,
URIRequirementBuilder.fromUri(serverUrl).build()
),
CredentialsMatchers.allOf(
AuthenticationTokens.matcher(GiteaAuth.class),
CredentialsMatchers.withId(credentialsId)
)
);
}

@Extension
public static class DescriptorImpl extends SCMSourceDescriptor {

static {
IconSet.icons.addIcon(
new Icon("icon-gitea-org icon-sm",
"plugin/gitea/images/16x16/gitea-org.png",
Icon.ICON_SMALL_STYLE));
IconSet.icons.addIcon(
new Icon("icon-gitea-org icon-md",
"plugin/gitea/images/24x24/gitea-org.png",
Icon.ICON_MEDIUM_STYLE));
IconSet.icons.addIcon(
new Icon("icon-gitea-org icon-lg",
"plugin/gitea/images/32x32/gitea-org.png",
Icon.ICON_LARGE_STYLE));
IconSet.icons.addIcon(
new Icon("icon-gitea-org icon-xlg",
"plugin/gitea/images/48x48/gitea-org.png",
Icon.ICON_XLARGE_STYLE));

IconSet.icons.addIcon(
new Icon("icon-gitea-logo icon-sm",
"plugin/gitea/images/16x16/gitea.png",
Icon.ICON_SMALL_STYLE));
IconSet.icons.addIcon(
new Icon("icon-gitea-logo icon-md",
"plugin/gitea/images/24x24/gitea.png",
Icon.ICON_MEDIUM_STYLE));
IconSet.icons.addIcon(
new Icon("icon-gitea-logo icon-lg",
"plugin/gitea/images/32x32/gitea.png",
Icon.ICON_LARGE_STYLE));
IconSet.icons.addIcon(
new Icon("icon-gitea-logo icon-xlg",
"plugin/gitea/images/48x48/gitea.png",
Icon.ICON_XLARGE_STYLE));

IconSet.icons.addIcon(
new Icon("icon-gitea-repo icon-sm",
"plugin/gitea/images/16x16/gitea-repo.png",
Icon.ICON_SMALL_STYLE));
IconSet.icons.addIcon(
new Icon("icon-gitea-repo icon-md",
"plugin/gitea/images/24x24/gitea-repo.png",
Icon.ICON_MEDIUM_STYLE));
IconSet.icons.addIcon(
new Icon("icon-gitea-repo icon-lg",
"plugin/gitea/images/32x32/gitea-repo.png",
Icon.ICON_LARGE_STYLE));
IconSet.icons.addIcon(
new Icon("icon-gitea-repo icon-xlg",
"plugin/gitea/images/48x48/gitea-repo.png",
Icon.ICON_XLARGE_STYLE));

IconSet.icons.addIcon(
new Icon("icon-gitea-branch icon-sm",
"plugin/gitea/images/16x16/gitea-branch.png",
Icon.ICON_SMALL_STYLE));
IconSet.icons.addIcon(
new Icon("icon-gitea-branch icon-md",
"plugin/gitea/images/24x24/gitea-branch.png",
Icon.ICON_MEDIUM_STYLE));
IconSet.icons.addIcon(
new Icon("icon-gitea-branch icon-lg",
"plugin/gitea/images/32x32/gitea-branch.png",
Icon.ICON_LARGE_STYLE));
IconSet.icons.addIcon(
new Icon("icon-gitea-branch icon-xlg",
"plugin/gitea/images/48x48/gitea-branch.png",
Icon.ICON_XLARGE_STYLE));
}

@Override
public String getDisplayName() {
return "Gitea";
}

public ListBoxModel doFillServerUrlItems(@AncestorInPath SCMSourceOwner context,
@QueryParameter String serverUrl) {
if (context == null) {
if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
// must have admin if you want the list without a context
ListBoxModel result = new ListBoxModel();
result.add(serverUrl);
return result;
}
} else {
if (!context.hasPermission(Item.EXTENDED_READ)) {
// must be able to read the configuration the list
ListBoxModel result = new ListBoxModel();
result.add(serverUrl);
return result;
}
}
return GiteaServers.get().getServerItems();
}

public ListBoxModel doFillCredentialsIdItems(@AncestorInPath SCMSourceOwner context,
@QueryParameter String serverUrl,
@QueryParameter String credentialsId) {
StandardListBoxModel result = new StandardListBoxModel();
if (context == null) {
if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
// must have admin if you want the list without a context
result.includeCurrentValue(credentialsId);
return result;
}
} else {
if (!context.hasPermission(Item.EXTENDED_READ)
&& !context.hasPermission(CredentialsProvider.USE_ITEM)) {
// must be able to read the configuration or use the item credentials if you want the list
result.includeCurrentValue(credentialsId);
return result;
}
}
result.includeEmptyValue();
result.includeMatchingAs(
context instanceof Queue.Task ?
((Queue.Task) context).getDefaultAuthentication()
: ACL.SYSTEM,
context,
StandardCredentials.class,
URIRequirementBuilder.fromUri(serverUrl).build(),
AuthenticationTokens.matcher(GiteaAuth.class)
);
return result;
}

public FormValidation doCheckCredentialsId(@AncestorInPath SCMSourceOwner context,
@QueryParameter String serverUrl,
@QueryParameter String value)
throws IOException, InterruptedException {
if (context == null) {
if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
return FormValidation.ok();
}
} else {
if (!context.hasPermission(Item.EXTENDED_READ)
&& !context.hasPermission(CredentialsProvider.USE_ITEM)) {
return FormValidation.ok();
}
}
GiteaServer server = GiteaServers.get().findServer(serverUrl);
if (server == null) {
return FormValidation.ok();
}
if (StringUtils.isBlank(value)) {
return FormValidation.ok();
}
if (CredentialsProvider.listCredentials(
StandardCredentials.class,
context,
context instanceof Queue.Task
? ((Queue.Task) context).getDefaultAuthentication()
: ACL.SYSTEM,
URIRequirementBuilder.fromUri(serverUrl).build(),
CredentialsMatchers.allOf(
CredentialsMatchers.withId(value),
AuthenticationTokens.matcher(GiteaAuth.class)

)).isEmpty()) {
return FormValidation.error(Messages.GiteaSCMSource_selectedCredentialsMissing());
}
return FormValidation.ok();
}

public ListBoxModel doFillRepositoryItems(@AncestorInPath SCMSourceOwner context,
@QueryParameter String serverUrl,
@QueryParameter String credentialsId,
@QueryParameter String repoOwner,
@QueryParameter String repository) throws IOException,
InterruptedException {
ListBoxModel result = new ListBoxModel();
if (context == null) {
if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
// must have admin if you want the list without a context
result.add(repository);
return result;
}
} else {
if (!context.hasPermission(Item.EXTENDED_READ)
&& !context.hasPermission(CredentialsProvider.USE_ITEM)) {
// must be able to read the configuration or use the item credentials if you want the list
result.add(repository);
return result;
}
}
if (StringUtils.isBlank(repoOwner)) {
result.add(repository);
return result;
}
GiteaServer server = GiteaServers.get().findServer(serverUrl);
if (server == null) {
// you can only get the list for registered servers
result.add(repository);
return result;
}
StandardCredentials credentials = CredentialsMatchers.firstOrNull(
CredentialsProvider.lookupCredentials(
StandardCredentials.class,
context,
context instanceof Queue.Task
? ((Queue.Task) context).getDefaultAuthentication()
: ACL.SYSTEM,
URIRequirementBuilder.fromUri(serverUrl).build()
),
CredentialsMatchers.allOf(
AuthenticationTokens.matcher(GiteaAuth.class),
CredentialsMatchers.withId(credentialsId)
)
);
try (GiteaConnection c = Gitea.server(serverUrl)
.as(AuthenticationTokens.convert(GiteaAuth.class, credentials))
.open()) {
GiteaOwner owner = c.fetchOwner(repoOwner);
List<GiteaRepository> repositories = c.fetchRepositories(owner);
for (GiteaRepository r : repositories) {
result.add(r.getName());
}
return result;
} catch (IOException e) {
// TODO once enhanced <f:select> that can handle error responses, just throw
LOGGER.log(Level.FINE, "Could not populate repositories", e);
if (result.isEmpty()) {
result.add(repository);
}
return result;
}
}

public List<NamedArrayList<? extends SCMTraitDescriptor<?>>> getTraitsDescriptorLists() {
List<SCMTraitDescriptor<?>> all = new ArrayList<>();
//all.addAll(SCMSourceTrait._for(this, GitHubSCMSourceContext.class, null));
all.addAll(SCMSourceTrait._for(this, null, GiteaSCMBuilder.class));
Set<SCMTraitDescriptor<?>> dedup = new HashSet<>();
for (Iterator<SCMTraitDescriptor<?>> iterator = all.iterator(); iterator.hasNext(); ) {
SCMTraitDescriptor<?> d = iterator.next();
if (dedup.contains(d)
|| d instanceof GitBrowserSCMSourceTrait.DescriptorImpl) {
// remove any we have seen already and ban the browser configuration as it will always be github
iterator.remove();
} else {
dedup.add(d);
}
}
List<NamedArrayList<? extends SCMTraitDescriptor<?>>> result = new ArrayList<>();
NamedArrayList.select(all, Messages.GiteaSCMSource_traitSection_withinRepo(), NamedArrayList
.anyOf(NamedArrayList.withAnnotation(Discovery.class),
NamedArrayList.withAnnotation(Selection.class)),
true, result);
NamedArrayList.select(all, Messages.GiteaSCMSource_traitSection_additional(), null, true, result);
return result;
}

public List<SCMSourceTrait> getTraitsDefaults() {
return Arrays.asList( // TODO finalize
new BranchDiscoveryTrait(true, false, false),
new OriginPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE)),
new ForkPullRequestDiscoveryTrait(EnumSet.of(ChangeRequestCheckoutStrategy.MERGE),
new ForkPullRequestDiscoveryTrait.TrustContributors())
);
}

@Override
public String getIconClassName() {
return "icon-gitea-repo";
}

@NonNull
@Override
protected SCMHeadCategory[] createCategories() {
return new SCMHeadCategory[]{
new UncategorizedSCMHeadCategory(Messages._GiteaSCMSource_UncategorizedCategory()),
new ChangeRequestSCMHeadCategory(Messages._GiteaSCMSource_ChangeRequestCategory()),
new TagSCMHeadCategory(Messages._GiteaSCMSource_TagCategory())
new TagSCMHeadCategory(Messages._GiteaSCMSource_TagCategory()),
new ReleaseSCMHeadCategory(Messages._GiteaSCMSource_ReleaseCategory())

Check warning on line 1031 in src/main/java/org/jenkinsci/plugin/gitea/GiteaSCMSource.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 265-1031 are not covered by tests
};
}
}
Expand Down
Loading
Loading