Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@
} catch (GitLabApiException e) {
LOGGER.log(Level.WARNING, "Exception while fetching members" + e, e);
return new HashMap<>();
} catch (InterruptedException e) {
throw new RuntimeException(e);

Check warning on line 241 in src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabSCMSource.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 240-241 are not covered by tests
}
return members;
}
Expand All @@ -252,7 +252,7 @@
try {
return gitLabApi.getProjectApi().getAllMembers(projectPath);
} catch (GitLabApiException | RuntimeException e) {
if (isRateLimitException(e)) {

Check warning on line 255 in src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabSCMSource.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 255 is only partially covered, one branch is missing
sleeper.sleep(delay);
delay *= 2;
attemptNb++;
Expand All @@ -261,471 +261,478 @@
}
continue;
}
throw e;

Check warning on line 264 in src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabSCMSource.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 264 is not covered by tests
}
}
}

private static boolean isRateLimitException(Exception e) {
if (e instanceof GitLabApiException) {

Check warning on line 270 in src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabSCMSource.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 270 is only partially covered, one branch is missing
return ((GitLabApiException) e).getHttpStatus() == 429;

Check warning on line 271 in src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabSCMSource.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 271 is only partially covered, one branch is missing
} else if (e.getCause() != null && e.getCause().getClass().isAssignableFrom(GitLabApiException.class)) {
GitLabApiException cause = (GitLabApiException) e.getCause();
return cause.getHttpStatus() == 429;
}
return false;
}

public Long getProjectId() {
return projectId;
}

@DataBoundSetter
public void setProjectId(Long projectId) {
this.projectId = projectId;
}

@NonNull
@Override
public List<SCMSourceTrait> getTraits() {
return Collections.unmodifiableList(traits);
}

@DataBoundSetter
public void setTraits(List<SCMSourceTrait> traits) {
this.traits = new ArrayList<>(Util.fixNull(traits));
}

@Override
protected SCMRevision retrieve(@NonNull SCMHead head, @NonNull TaskListener listener)
throws IOException, InterruptedException {
try {
GitLabApi gitLabApi = apiBuilder(this.getOwner(), serverName);
getGitlabProject(gitLabApi);
if (head instanceof BranchSCMHead) {
listener.getLogger().format("Querying the current revision of branch %s...%n", head.getName());
String revision = gitLabApi
.getRepositoryApi()
.getBranch(gitlabProject, head.getName())
.getCommit()
.getId();
listener.getLogger().format("Current revision of branch %s is %s%n", head.getName(), revision);
return new BranchSCMRevision((BranchSCMHead) head, revision);
} else if (head instanceof MergeRequestSCMHead) {
MergeRequestSCMHead h = (MergeRequestSCMHead) head;
listener.getLogger().format("Querying the current revision of merge request #%s...%n", h.getId());
MergeRequest mr =
gitLabApi.getMergeRequestApi().getMergeRequest(gitlabProject, Long.parseLong(h.getId()));
String targetSha = gitLabApi
.getRepositoryApi()
.getBranch(mr.getTargetProjectId(), mr.getTargetBranch())
.getCommit()
.getId();
if (mr.getState().equals(Constants.MergeRequestState.OPENED.toString())) {
listener.getLogger()
.format("Current revision of merge request #%s is %s%n", h.getId(), mr.getSha());
return new MergeRequestSCMRevision(
h,
new BranchSCMRevision(h.getTarget(), targetSha),
new BranchSCMRevision(new BranchSCMHead(h.getOriginName()), mr.getSha()));
} else {
listener.getLogger().format("Merge request #%s is CLOSED%n", h.getId());
return null;
}
} else if (head instanceof GitLabTagSCMHead) {
listener.getLogger().format("Querying the current revision of tag %s...%n", head.getName());
String revision = gitLabApi
.getTagsApi()
.getTag(gitlabProject, head.getName())
.getCommit()
.getId();
listener.getLogger().format("Current revision of tag %s is %s%n", head.getName(), revision);
return new GitTagSCMRevision((GitLabTagSCMHead) head, revision);
} else {
listener.getLogger()
.format(
"Unknown head: %s of type %s%n",
head.getName(), head.getClass().getName());
return null;
}
} catch (GitLabApiException e) {
LOGGER.log(Level.WARNING, "Exception caught:" + e, e);
throw new IOException("Failed to retrieve the SCM revision for " + head.getName(), e);
}
}

@Override
protected void retrieve(
SCMSourceCriteria criteria,
@NonNull SCMHeadObserver observer,
SCMHeadEvent<?> event,
@NonNull TaskListener listener)
throws IOException, InterruptedException {
try {
GitLabApi gitLabApi = apiBuilder(this.getOwner(), serverName);
getGitlabProject(gitLabApi);
GitLabSCMSourceContext ctx = new GitLabSCMSourceContext(criteria, observer).withTraits(getTraits());
try (GitLabSCMSourceRequest request = ctx.newRequest(this, listener)) {
request.setGitLabApi(gitLabApi);
request.setProject(gitlabProject);
request.setMembers(getMembers());
if (request.isFetchBranches()) {
request.setBranches(gitLabApi.getRepositoryApi().getBranches(gitlabProject));
}
boolean mergeRequestsEnabled = !Boolean.FALSE.equals(gitlabProject.getMergeRequestsEnabled());
if (request.isFetchMRs() && mergeRequestsEnabled) {
final boolean forkedFromProject = (gitlabProject.getForkedFromProject() != null);
if (!ctx.buildMRForksNotMirror() && forkedFromProject) {
listener.getLogger().format("%nIgnoring merge requests as project is a mirror...%n");
} else {
// If not authenticated GitLabApi cannot detect if it is a fork
// If `forkedFromProject` is false it doesn't mean anything
listener.getLogger()
.format(
!forkedFromProject
? "%nUnable to detect if it is a mirror or not still fetching MRs anyway...%n"
: "%nCollecting MRs for fork except those that target its upstream...%n");
Stream<MergeRequest> mrs =
gitLabApi
.getMergeRequestApi()
.getMergeRequests(gitlabProject, MergeRequestState.OPENED)
.stream()
.filter(mr -> mr.getSourceProjectId() != null);
// Patch for issue 453 - avoid an NPE if this isn't a forked project
if (ctx.buildMRForksNotMirror() && forkedFromProject) {
mrs = mrs.filter(mr -> !mr.getTargetProjectId()
.equals(gitlabProject.getForkedFromProject().getId()));
}

if (ctx.alwaysIgnoreMRWorkInProgress()) {
mrs = mrs.filter(mr -> !mr.getWorkInProgress());
}

request.setMergeRequests(mrs.collect(Collectors.toList()));
}
}
if (request.isFetchTags()) {
request.setTags(gitLabApi.getTagsApi().getTags(gitlabProject));
}
if (request.isFetchBranches()) {
int count = 0;
listener.getLogger().format("%nChecking branches.. %n");
Iterable<Branch> branches = request.getBranches();
for (final Branch branch : branches) {
count++;
String branchName = branch.getName();
String sha = branch.getCommit().getId();
listener.getLogger()
.format(
"%nChecking branch %s%n",
HyperlinkNote.encodeTo(
branchUriTemplate(gitlabProject.getWebUrl())
.set("branch", splitPath(branchName))
.expand(),
branchName));
if (request.process(
new BranchSCMHead(branchName),
(SCMSourceRequest.RevisionLambda<BranchSCMHead, BranchSCMRevision>)
head -> new BranchSCMRevision(head, sha),
new SCMSourceRequest.ProbeLambda<BranchSCMHead, BranchSCMRevision>() {
@NonNull
@Override
public SCMSourceCriteria.Probe create(
@NonNull BranchSCMHead head, @Nullable BranchSCMRevision revision)
throws IOException {
return createProbe(head, revision);
}
},
(SCMSourceRequest.Witness) (head, revision, isMatch) -> {
if (isMatch) {
listener.getLogger().format("Met criteria%n");
} else {
listener.getLogger().format("Does not meet criteria%n");
}
})) {
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.isFetchMRs() && !request.isComplete() && mergeRequestsEnabled) {
int count = 0;
listener.getLogger().format("%nChecking merge requests..%n");
HashMap<Long, String> forkMrSources = new HashMap<>();
for (MergeRequest mr : request.getMergeRequests()) {
mergeRequestContributorCache.put(
mr.getIid(),
new ContributorMetadataAction(
mr.getAuthor().getUsername(),
mr.getAuthor().getName(),
mr.getAuthor().getEmail()));
mergeRequestMetadataCache.put(
mr.getIid(),
new ObjectMetadataAction(mr.getTitle(), mr.getDescription(), mr.getWebUrl()));
count++;
listener.getLogger()
.format(
"%nChecking merge request %s%n",
HyperlinkNote.encodeTo(
mergeRequestUriTemplate(gitlabProject.getWebUrl())
.set("iid", mr.getIid())
.expand(),
"!" + mr.getIid()));
Map<Boolean, Set<ChangeRequestCheckoutStrategy>> strategies = request.getMRStrategies();
boolean fork = !mr.getSourceProjectId().equals(mr.getTargetProjectId());
String originOwner = mr.getAuthor().getUsername();
String originProjectPath = projectPath;
if (fork && !forkMrSources.containsKey(mr.getSourceProjectId())) {
// This is a hack to get the path with namespace of source project for forked
// mrs
try {
originProjectPath = gitLabApi
.getProjectApi()
.getProject(mr.getSourceProjectId())
.getPathWithNamespace();
forkMrSources.put(mr.getSourceProjectId(), originProjectPath);
} catch (GitLabApiException e) {
if (e.getHttpStatus() == 404) {
listener.getLogger()
.format(
"%nIgnoring merge requests as source project not found, Please check permission on source repo...%n");
continue;
} else {
throw e;
}
}
} else if (fork) {
originProjectPath = forkMrSources.get(mr.getSourceProjectId());
}
String targetSha;
try {
targetSha = gitLabApi
.getRepositoryApi()
.getBranch(mr.getTargetProjectId(), mr.getTargetBranch())
.getCommit()
.getId();
} catch (Exception e) {
listener.getLogger()
.format(
"Failed getting TargetBranch from Merge Request: " + mr.getIid() + " ("
+ mr.getTitle() + ")%n%s",
e);
continue;
}
LOGGER.log(
Level.FINE,
String.format(
"%s -> %s",
originOwner, (request.isMember(originOwner) ? "Trusted" : "Untrusted")));
for (ChangeRequestCheckoutStrategy strategy : strategies.get(fork)) {
if (request.process(
new MergeRequestSCMHead(
"MR-" + mr.getIid()
+ (strategies.get(fork).size() > 1
? "-"
+ strategy.name()
.toLowerCase(Locale.ENGLISH)
: ""),
mr.getIid(),
new BranchSCMHead(mr.getTargetBranch()),
strategy,
fork ? new SCMHeadOrigin.Fork(originProjectPath) : SCMHeadOrigin.DEFAULT,
originOwner,
originProjectPath,
mr.getSourceBranch(),
mr.getTitle()),
(SCMSourceRequest.RevisionLambda<MergeRequestSCMHead, MergeRequestSCMRevision>)
head -> new MergeRequestSCMRevision(
head,
new BranchSCMRevision(
head.getTarget(),
targetSha // Latest revision of target branch
),
new BranchSCMRevision(
new BranchSCMHead(head.getOriginName()), mr.getSha())),
new SCMSourceRequest.ProbeLambda<MergeRequestSCMHead, MergeRequestSCMRevision>() {
@NonNull
@Override
public SCMSourceCriteria.Probe create(
@NonNull MergeRequestSCMHead head,
@Nullable MergeRequestSCMRevision revision)
throws IOException, InterruptedException {
boolean isTrusted = request.isTrusted(head);
if (!isTrusted) {
listener.getLogger().format("(not from a trusted source)%n");
}
return createProbe(isTrusted ? head : head.getTarget(), revision);
}
},
(SCMSourceRequest.Witness) (head, revision, isMatch) -> {
if (isMatch) {
listener.getLogger().format("Met criteria%n");
} else {
listener.getLogger().format("Does not meet criteria%n");
}
})) {
listener.getLogger()
.format("%n%d merge requests were processed (query completed)%n", count);
return;
}
}
}
listener.getLogger().format("%n%d merge requests were processed%n", count);
}
if (request.isFetchTags()) {
int count = 0;
listener.getLogger().format("%nChecking tags..%n");
Iterable<Tag> tags = request.getTags();
for (Tag tag : tags) {
count++;
String tagName = tag.getName();
Long tagDate = tag.getCommit().getCommittedDate().getTime();
String sha = tag.getCommit().getId();
listener.getLogger()
.format(
"%nChecking tag %s%n",
HyperlinkNote.encodeTo(
tagUriTemplate(gitlabProject.getWebUrl())
.set("tag", splitPath(tag.getName()))
.expand(),
tag.getName()));
GitLabTagSCMHead head = new GitLabTagSCMHead(tagName, tagDate);
if (request.process(
head,
new GitTagSCMRevision(head, sha),
new SCMSourceRequest.ProbeLambda<GitLabTagSCMHead, GitTagSCMRevision>() {
@NonNull
@Override
public SCMSourceCriteria.Probe create(
@NonNull GitLabTagSCMHead head, @Nullable GitTagSCMRevision revision)
throws IOException {
return createProbe(head, revision);
}
},
(SCMSourceRequest.Witness) (head1, revision, isMatch) -> {
if (isMatch) {
listener.getLogger().format("Met criteria%n");
} else {
listener.getLogger().format("Does not meet criteria%n");
}
})) {
listener.getLogger().format("%n%d tags were processed (query completed)%n", count);
return;
}
}
listener.getLogger().format("%n%d tags were processed (query completed)%n", count);
}
}
} catch (GitLabApiException e) {
LOGGER.log(Level.WARNING, "Exception caught:" + e, e);
throw new IOException("Failed to fetch latest heads", e);
} finally {
SCMSourceOwner owner = this.getOwner();
if (owner != null) {
owner.save();
}
}
}

@Override
protected SCMRevision retrieve(@NonNull String thingName, @NonNull TaskListener listener)
throws IOException, InterruptedException {
SCMHeadObserver.Named baptist = SCMHeadObserver.named(thingName);
retrieve(null, baptist, null, listener);
return baptist.result();
}

@NonNull
@Override
protected Set<String> retrieveRevisions(@NonNull TaskListener listener) throws IOException, InterruptedException {
// don't pass through to git, instead use the super.super behaviour
Set<String> revisions = new HashSet<>();
for (SCMHead head : retrieve(listener)) {
revisions.add(head.getName());
}
return revisions;
}

@NonNull
@Override
protected List<Action> retrieveActions(SCMSourceEvent event, @NonNull TaskListener listener) {
List<Action> result = new ArrayList<>();
getGitlabProject();
GitLabSCMSourceContext ctx = new GitLabSCMSourceContext(null, SCMHeadObserver.none()).withTraits(traits);
String projectUrl = gitlabProject.getWebUrl();
String name = StringUtils.isBlank(projectName) ? gitlabProject.getNameWithNamespace() : projectName;
result.add(new ObjectMetadataAction(name, gitlabProject.getDescription(), projectUrl));
String avatarUrl = gitlabProject.getAvatarUrl();
if (!ctx.projectAvatarDisabled() && StringUtils.isNotBlank(avatarUrl)) {
result.add(new GitLabAvatar(avatarUrl));
}
result.add(GitLabLink.toProject(projectUrl));
return result;
}

@NonNull
@Override
protected List<Action> retrieveActions(@NonNull SCMHead head, SCMHeadEvent event, @NonNull TaskListener listener) {
getGitlabProject();
List<Action> result = new ArrayList<>();
if (head instanceof BranchSCMHead) {
String branchUrl = branchUriTemplate(gitlabProject.getWebUrl())
.set("branch", head.getName())
.expand();
result.add(new ObjectMetadataAction(null, null, branchUrl));
result.add(GitLabLink.toBranch(branchUrl));
if (head.getName().equals(gitlabProject.getDefaultBranch())) {
result.add(new PrimaryInstanceMetadataAction());
}
} else if (head instanceof MergeRequestSCMHead) {
long iid = Long.parseLong(((MergeRequestSCMHead) head).getId());
String mergeUrl = mergeRequestUriTemplate(gitlabProject.getWebUrl())
.set("iid", iid)
.expand();
ObjectMetadataAction metadataAction = mergeRequestMetadataCache.get(iid);
if (metadataAction == null) {
// best effort
metadataAction = new ObjectMetadataAction(null, null, mergeUrl);
}
result.add(metadataAction);
ContributorMetadataAction contributor = mergeRequestContributorCache.get(iid);
if (contributor != null) {
result.add(contributor);
}
result.add(GitLabLink.toMergeRequest(mergeUrl));
} else if (head instanceof GitLabTagSCMHead) {
String tagUrl = tagUriTemplate(gitlabProject.getWebUrl())
.set("tag", head.getName())
.expand();
result.add(new ObjectMetadataAction(null, null, tagUrl));
result.add(GitLabLink.toTag(tagUrl));
}
return result;
}

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

@NonNull
@Override
public SCMRevision getTrustedRevision(@NonNull SCMRevision revision, @NonNull TaskListener listener) {
if (revision instanceof MergeRequestSCMRevision) {
MergeRequestSCMHead head = (MergeRequestSCMHead) revision.getHead();
try (GitLabSCMSourceRequest request = new GitLabSCMSourceContext(null, SCMHeadObserver.none())
.withTraits(traits)
.newRequest(this, listener)) {
request.setMembers(getMembers());
boolean isTrusted = request.isTrusted(head);
LOGGER.log(Level.FINEST, String.format("Trusted Revision: %s -> %s", head.getOriginOwner(), isTrusted));
if (isTrusted) {
return revision;
if (SCMHeadOrigin.Default.class.isAssignableFrom(head.getOrigin().getClass())) {
return revision;
} else if (SCMHeadOrigin.Fork.class.isAssignableFrom(
head.getOrigin().getClass())) {
try (GitLabSCMSourceRequest request = new GitLabSCMSourceContext(null, SCMHeadObserver.none())
.withTraits(traits)
.newRequest(this, listener)) {
request.setMembers(getMembers());
boolean isTrusted = request.isTrusted(head);
LOGGER.log(
Level.FINEST,
String.format("Trusted Revision: %s -> %s", head.getOriginOwner(), isTrusted));

Check warning on line 729 in src/main/java/io/jenkins/plugins/gitlabbranchsource/GitLabSCMSource.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 272-729 are not covered by tests
if (isTrusted) {
return revision;
}
} catch (IOException | InterruptedException e) {
LOGGER.log(Level.SEVERE, "Exception caught: " + e, e);
}
} catch (IOException | InterruptedException e) {
LOGGER.log(Level.SEVERE, "Exception caught: " + e, e);
}
MergeRequestSCMRevision rev = (MergeRequestSCMRevision) revision;
listener.getLogger()
Expand Down
Loading