/* * The MIT License * * Copyright (c) 2016 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 jenkins.scm.impl.mock; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.model.Action; import hudson.model.TaskListener; import hudson.scm.SCM; import hudson.util.ListBoxModel; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Set; import javax.annotation.Nonnull; import jenkins.scm.api.SCMHead; import jenkins.scm.api.SCMHeadCategory; import jenkins.scm.api.SCMHeadEvent; import jenkins.scm.api.SCMHeadObserver; import jenkins.scm.api.SCMHeadOrigin; import jenkins.scm.api.SCMProbe; import jenkins.scm.api.SCMProbeStat; import jenkins.scm.api.SCMRevision; import jenkins.scm.api.SCMSource; import jenkins.scm.api.SCMSourceCriteria; import jenkins.scm.api.SCMSourceDescriptor; import jenkins.scm.api.SCMSourceEvent; import jenkins.scm.api.metadata.ContributorMetadataAction; import jenkins.scm.api.metadata.ObjectMetadataAction; import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy; import jenkins.scm.impl.ChangeRequestSCMHeadCategory; import jenkins.scm.impl.TagSCMHeadCategory; import jenkins.scm.impl.UncategorizedSCMHeadCategory; import org.codehaus.plexus.util.StringUtils; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; public class MockSCMSource extends SCMSource { private final String controllerId; private final String repository; private final boolean includeBranches; private final boolean includeTags; private final boolean includeChangeRequests; private Set<ChangeRequestCheckoutStrategy> strategies = EnumSet.of(ChangeRequestCheckoutStrategy.HEAD); private transient MockSCMController controller; @DataBoundConstructor public MockSCMSource(@CheckForNull String id, String controllerId, String repository, boolean includeBranches, boolean includeTags, boolean includeChangeRequests) { super(id); this.controllerId = controllerId; this.repository = repository; this.includeBranches = includeBranches; this.includeTags = includeTags; this.includeChangeRequests = includeChangeRequests; } public MockSCMSource(String id, MockSCMController controller, String repository, boolean includeBranches, boolean includeTags, boolean includeChangeRequests) { super(id); this.controllerId = controller.getId(); this.controller = controller; this.repository = repository; this.includeBranches = includeBranches; this.includeTags = includeTags; this.includeChangeRequests = includeChangeRequests; } public String getControllerId() { return controllerId; } private MockSCMController controller() { if (controller == null) { controller = MockSCMController.lookup(controllerId); } return controller; } public String getStrategiesStr() { StringBuilder r = new StringBuilder(); for (ChangeRequestCheckoutStrategy s: strategies) { r.append(s.name()).append(", "); } return r.toString(); } @DataBoundSetter public void setStrategiesStr(String strategiesStr) { Set<ChangeRequestCheckoutStrategy> strategies = EnumSet.noneOf(ChangeRequestCheckoutStrategy.class); for (String s : StringUtils.split(strategiesStr, ", ")) { try { strategies.add(ChangeRequestCheckoutStrategy.valueOf(s.trim())); } catch (IllegalArgumentException e) { // ignore } } setStrategies(strategies); } public String getRepository() { return repository; } public boolean isIncludeBranches() { return includeBranches; } public boolean isIncludeTags() { return includeTags; } public boolean isIncludeChangeRequests() { return includeChangeRequests; } @Override protected void retrieve(@CheckForNull SCMSourceCriteria criteria, @NonNull SCMHeadObserver observer, @CheckForNull SCMHeadEvent<?> event, @NonNull TaskListener listener) throws IOException, InterruptedException { controller().applyLatency(); controller().checkFaults(repository, null, null, false); Set<SCMHead> includes = observer.getIncludes(); if (includeBranches) { for (final String branch : controller().listBranches(repository)) { checkInterrupt(); String revision = controller().getRevision(repository, branch); MockSCMHead head = new MockSCMHead(branch); if (includes != null && !includes.contains(head)) { continue; } controller().applyLatency(); controller().checkFaults(repository, head.getName(), null, false); if (criteria == null || criteria.isHead(new MockSCMProbe(head, revision), listener)) { controller().applyLatency(); controller().checkFaults(repository, head.getName(), revision, false); observer.observe(head, new MockSCMRevision(head, revision)); } } } if (includeTags) { for (final String tag : controller().listTags(repository)) { checkInterrupt(); String revision = controller().getRevision(repository, tag); MockSCMHead head = new MockTagSCMHead(tag, controller().getTagTimestamp(repository, tag)); if (includes != null && !includes.contains(head)) { continue; } controller().applyLatency(); controller().checkFaults(repository, head.getName(), null, false); if (criteria == null || criteria.isHead(new MockSCMProbe(head, revision), listener)) { controller().applyLatency(); controller().checkFaults(repository, head.getName(), revision, false); observer.observe(head, new MockSCMRevision(head, revision)); } } } if (includeChangeRequests) { for (final Integer number : controller().listChangeRequests(repository)) { checkInterrupt(); Set<MockRepositoryFlags> repoFlags = controller().getFlags(repository); String revision = controller().getRevision(repository, "change-request/" + number); String target = controller().getTarget(repository, number); String targetRevision = controller().getRevision(repository, target); Set<MockChangeRequestFlags> crFlags = controller.getFlags(repository, number); boolean singleStrategy = strategies.size() == 1; for (ChangeRequestCheckoutStrategy strategy : strategies) { MockChangeRequestSCMHead head = new MockChangeRequestSCMHead( crFlags.contains(MockChangeRequestFlags.FORK) ? new SCMHeadOrigin.Fork("fork") : null, number, target, strategy, singleStrategy); if (includes != null && !includes.contains(head)) { continue; } controller().applyLatency(); controller().checkFaults(repository, head.getName(), null, false); if (criteria == null || criteria.isHead(new MockSCMProbe(head, revision), listener)) { controller().applyLatency(); controller().checkFaults(repository, head.getName(), revision, false); observer.observe(head, new MockChangeRequestSCMRevision(head, new MockSCMRevision(head.getTarget(), targetRevision), revision)); } } } } } @NonNull @Override public SCM build(@NonNull SCMHead head, @CheckForNull SCMRevision revision) { if (revision instanceof MockSCMRevision || revision instanceof MockChangeRequestSCMRevision) { return new MockSCM(this, head, revision); } return new MockSCM(this, head, null); } @NonNull @Override protected List<Action> retrieveActions(@CheckForNull SCMSourceEvent event, @NonNull TaskListener listener) throws IOException, InterruptedException { controller().applyLatency(); controller().checkFaults(repository, null, null, true); List<Action> result = new ArrayList<Action>(); result.add(new MockSCMLink("source")); String description = controller().getDescription(repository); String displayName = controller().getDisplayName(repository); String url = controller().getUrl(repository); String iconClassName = controller().getRepoIconClassName(); if (description != null || displayName != null || url != null) { result.add(new ObjectMetadataAction(displayName, description, url)); } if (iconClassName != null) { result.add(new MockAvatarMetadataAction(iconClassName)); } return result; } @NonNull @Override protected List<Action> retrieveActions(@NonNull SCMRevision revision, @CheckForNull SCMHeadEvent event, @NonNull TaskListener listener) throws IOException, InterruptedException { controller().applyLatency(); String hash ; if (revision instanceof MockSCMRevision) { hash = ((MockSCMRevision) revision).getHash(); } else if (revision instanceof MockChangeRequestSCMRevision) { hash = ((MockChangeRequestSCMRevision) revision).getHash(); } else { throw new IOException("Unexpected revision"); } controller().checkFaults(repository, revision.getHead().getName(), hash, true); return Collections.<Action>singletonList(new MockSCMLink("revision")); } @NonNull @Override protected List<Action> retrieveActions(@NonNull SCMHead head, @CheckForNull SCMHeadEvent event, @NonNull TaskListener listener) throws IOException, InterruptedException { controller().applyLatency(); controller().checkFaults(repository, head.getName(), null, true); List<Action> result = new ArrayList<Action>(); if (head instanceof MockChangeRequestSCMHead) { result.add(new ContributorMetadataAction( "bob", "Bob Smith", "bob@example.com" )); result.add(new ObjectMetadataAction( String.format("Change request #%d", ((MockChangeRequestSCMHead) head).getNumber()), null, "http://changes.example.com/" + ((MockChangeRequestSCMHead) head).getId() )); } result.add(new MockSCMLink("branch")); return result; } @Override protected boolean isCategoryEnabled(@NonNull SCMHeadCategory category) { if (category instanceof ChangeRequestSCMHeadCategory) { return includeChangeRequests; } if (category instanceof TagSCMHeadCategory) { return includeTags; } return true; } public Set<ChangeRequestCheckoutStrategy> getStrategies() { return Collections.unmodifiableSet(strategies); } public void setStrategies(@NonNull Set<ChangeRequestCheckoutStrategy> strategies) { this.strategies = strategies.isEmpty() ? EnumSet.noneOf(ChangeRequestCheckoutStrategy.class) : EnumSet.copyOf(strategies); } @Extension public static class DescriptorImpl extends SCMSourceDescriptor { @Nonnull @Override public String getDisplayName() { return "Mock SCM"; } public ListBoxModel doFillControllerIdItems() { ListBoxModel result = new ListBoxModel(); for (MockSCMController c : MockSCMController.all()) { result.add(c.getId()); } return result; } public ListBoxModel doFillRepositoryItems(@QueryParameter String controllerId) throws IOException { ListBoxModel result = new ListBoxModel(); MockSCMController c = MockSCMController.lookup(controllerId); if (c != null) { for (String r : c.listRepositories()) { result.add(r); } } return result; } @NonNull @Override protected SCMHeadCategory[] createCategories() { return new SCMHeadCategory[]{ UncategorizedSCMHeadCategory.DEFAULT, ChangeRequestSCMHeadCategory.DEFAULT, TagSCMHeadCategory.DEFAULT }; } } private class MockSCMProbe extends SCMProbe { private final String revision; private final SCMHead head; public MockSCMProbe(SCMHead head, String revision) { this.revision = revision; this.head = head; } @NonNull @Override public SCMProbeStat stat(@NonNull String path) throws IOException { return SCMProbeStat.fromType(controller().stat(repository, revision, path)); } @Override public void close() throws IOException { } @Override public String name() { return head.getName(); } @Override public long lastModified() { return controller().lastModified(repository, revision); } } }