Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
85 changes: 50 additions & 35 deletions src/main/java/hudson/scm/SubversionSCM.java
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ public SubversionSCM(List<ModuleLocation> locations,
*/
public SubversionSCM(List<ModuleLocation> locations,
boolean useUpdate, SubversionRepositoryBrowser browser, String excludedRegions, String excludedUsers, String excludedRevprop, String excludedCommitMessages) {
this(locations, useUpdate, false, browser, excludedRegions, excludedUsers, excludedRevprop, excludedCommitMessages);
this(locations, useUpdate, false, browser, excludedRegions, excludedUsers, excludedRevprop, excludedCommitMessages);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May cause merge conflicts without a reason

}

/**
Expand Down Expand Up @@ -372,28 +372,35 @@ public SubversionSCM(List<ModuleLocation> locations, WorkspaceUpdater workspaceU
* Convenience constructor, especially during testing.
*/
public SubversionSCM(String svnUrl) {
this(svnUrl, null, ".");
this(svnUrl, null, ".", new ArrayList<AdditionalCredentials>());
}

/**
* Convenience constructor, especially during testing.
*/
public SubversionSCM(String svnUrl, String local) {
this(svnUrl, null, local);
this(svnUrl, null, local, new ArrayList<AdditionalCredentials>());
}

/**
* Convenience constructor, especially during testing.
*/
public SubversionSCM(String svnUrl, String credentialId, String local) {
this(new String[]{svnUrl}, new String[]{credentialId}, new String[]{local});
this(new String[]{svnUrl}, new String[]{credentialId}, new String[]{local}, new ArrayList<AdditionalCredentials>());
}

/**
* Convenience constructor, especially during testing.
*/
public SubversionSCM(String svnUrl, String credentialId, String local, List<AdditionalCredentials> additionalCredentials) {
this(new String[]{svnUrl}, new String[]{credentialId}, new String[]{local}, additionalCredentials);
}

/**
* Convenience constructor, especially during testing.
*/
public SubversionSCM(String[] svnUrls, String[] locals) {
this(svnUrls, null, locals);
this(svnUrls, null, locals, new ArrayList<AdditionalCredentials>());
}

/**
Expand All @@ -403,6 +410,14 @@ public SubversionSCM(String[] svnUrls, String[] credentialIds, String[] locals)
this(ModuleLocation.parse(svnUrls, credentialIds, locals, null,null), true, false, null, null, null, null, null);
}


/**
* Convenience constructor, especially during testing.
*/
public SubversionSCM(String[] svnUrls, String[] credentialIds, String[] locals, List<AdditionalCredentials> additionalCredentials) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not believe new methods are really required if you implement proper null handling in the original constructors. You say these methods are useful for testing, but there is no autotests

this(ModuleLocation.parse(svnUrls, credentialIds, locals, null,null), new UpdateUpdater(), null, null, null, null, null, null, false, false, additionalCredentials);
}

/**
* @deprecated
* as of 1.91. Use {@link #getLocations()} instead.
Expand All @@ -418,7 +433,7 @@ public String getModules() {
*/
@Exported
public ModuleLocation[] getLocations() {
return getLocations(null, null);
return getLocations(null, null);
}

@Override public String getKey() {
Expand Down Expand Up @@ -777,36 +792,36 @@ private void calcChangeLog(Run<?,?> build, FilePath workspace, File changelogFil
try {
String line;
while((line=br.readLine())!=null) {
boolean isPinned = false;
int indexLast = line.length();
if (line.lastIndexOf("::p") == indexLast-3) {
isPinned = true;
indexLast -= 3;
}
int index = line.lastIndexOf('/');
boolean isPinned = false;
int indexLast = line.length();
if (line.lastIndexOf("::p") == indexLast-3) {
isPinned = true;
indexLast -= 3;
}
int index = line.lastIndexOf('/');
if(index<0) {
continue; // invalid line?
}
try {
String url = line.substring(0, index);
long revision = Long.parseLong(line.substring(index+1,indexLast));
Long oldRevision = revisions.get(url);
if (isPinned) {
if (!prunePinnedExternals) {
if (oldRevision == null)
// If we're writing pinned, only write if there are no unpinned
revisions.put(url, revision);
}
} else {
// unpinned
if (oldRevision == null || oldRevision > revision)
// For unpinned, take minimum
revisions.put(url, revision);
}
} catch (NumberFormatException e) {
// perhaps a corrupted line.
LOGGER.log(WARNING, "Error parsing line " + line, e);
}
String url = line.substring(0, index);
long revision = Long.parseLong(line.substring(index+1,indexLast));
Long oldRevision = revisions.get(url);
if (isPinned) {
if (!prunePinnedExternals) {
if (oldRevision == null)
// If we're writing pinned, only write if there are no unpinned
revisions.put(url, revision);
}
} else {
// unpinned
if (oldRevision == null || oldRevision > revision)
// For unpinned, take minimum
revisions.put(url, revision);
}
} catch (NumberFormatException e) {
// perhaps a corrupted line.
LOGGER.log(WARNING, "Error parsing line " + line, e);
}
}
} finally {
br.close();
Expand Down Expand Up @@ -2759,7 +2774,7 @@ public String getLocalDir() {
* possible "@NNN" suffix.
*/
public String getURL() {
return SvnHelper.getUrlWithoutRevision(remote);
return SvnHelper.getUrlWithoutRevision(remote);
}

/**
Expand Down Expand Up @@ -3237,7 +3252,7 @@ private static StandardCredentials lookupCredentials(Item context, String creden
URIRequirementBuilder.fromUri(repoURL.toString()).build()),
CredentialsMatchers.withId(credentialsId));
}

public static class AdditionalCredentials extends AbstractDescribableImpl<AdditionalCredentials> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, you must leave this class where it was for reasons of settings compatibility. Nothing is stopping SubversionSCMSource from reusing it here.

@NonNull
private final String realm;
Expand Down Expand Up @@ -3331,5 +3346,5 @@ public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Item context,
}

}
}
}
}
40 changes: 34 additions & 6 deletions src/main/java/jenkins/scm/impl/subversion/SubversionSCMSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import hudson.scm.FilterSVNAuthenticationManager;
import hudson.scm.SubversionRepositoryStatus;
import hudson.scm.SubversionSCM;
import hudson.scm.SubversionSCM.AdditionalCredentials;
import hudson.scm.subversion.SvnHelper;
import hudson.security.ACL;
import hudson.util.EditDistance;
Expand Down Expand Up @@ -121,6 +122,8 @@ public class SubversionSCMSource extends SCMSource {
private final String remoteBase;

private String credentialsId = ""; // TODO null would be a better default, but need to check null safety on usages

private List<AdditionalCredentials> additionalCredentials;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from what I see it still may be null when you read data from the disk


private String includes = DescriptorImpl.DEFAULT_INCLUDES;

Expand All @@ -136,15 +139,18 @@ public SubversionSCMSource(String id, String remoteBase, String credentialsId, S
setCredentialsId(credentialsId);
setIncludes(StringUtils.defaultIfEmpty(includes, DescriptorImpl.DEFAULT_INCLUDES));
setExcludes(StringUtils.defaultIfEmpty(excludes, DescriptorImpl.DEFAULT_EXCLUDES));
this.additionalCredentials = new ArrayList<AdditionalCredentials>();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usage of this collection is ineffective if you do not plan to modify it on the flight. Use Collections.emptyList() instead in the code

}

@DataBoundConstructor
@SuppressWarnings("unused") // by stapler
public SubversionSCMSource(String id, String remoteBase) {
super(id);
this.remoteBase = StringUtils.removeEnd(remoteBase, "/") + "/";
this.additionalCredentials = new ArrayList<AdditionalCredentials>();
}


/**
* Gets the credentials id.
*
Expand All @@ -159,6 +165,14 @@ public String getCredentialsId() {
public void setCredentialsId(String credentialsId) {
this.credentialsId = credentialsId;
}
@DataBoundSetter
public void setAdditionalCredentials(List<AdditionalCredentials> additionalCredentials) {
this.additionalCredentials = additionalCredentials;
}
//without getter jelly will crash
public List<AdditionalCredentials> getAdditionalCredentials() {
return(additionalCredentials);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FindBugs will grumble about exposing external implementation. Either make it @Restricted for Jelly only or wrap with unmodifyableList

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your comments. How do I make it restricted? Does adding "@restricted(NoExternalUse.class)" work?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be better to just return Collections.unmodifyableList(additionalCredentials)

}

/**
* Gets the comma separated list of exclusions.
Expand Down Expand Up @@ -273,7 +287,7 @@ protected SCMRevision retrieve(@NonNull SCMHead head, @NonNull TaskListener list
String repoPath = SubversionSCM.DescriptorImpl.getRelativePath(repoURL, repository.getRepository());
String path = SVNPathUtil.append(repoPath, head.getName());
SVNRepositoryView.NodeEntry svnEntry = repository.getNode(path, -1);
return new SCMRevisionImpl(head, svnEntry.getRevision());
return new SCMRevisionImpl(head, svnEntry.getRevision(), (additionalCredentials.size()>0));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not precise. Additional credentials might have been specified but not actually used. More plausibly, the server may use externals which simply do not need any distinct credentials.

} catch (SVNException e) {
throw new IOException(e);
} finally {
Expand Down Expand Up @@ -308,7 +322,7 @@ protected SCMRevision retrieve(String unparsedRevision, TaskListener listener) t
listener.getLogger().println("Could not find " + path);
return null;
}
return new SCMRevisionImpl(new SCMHead(base), revision == -1 ? resolvedRevision : revision);
return new SCMRevisionImpl(new SCMHead(base), revision == -1 ? resolvedRevision : revision, (additionalCredentials.size()>0));
} catch (SVNException e) {
throw new IOException(e);
} finally {
Expand Down Expand Up @@ -409,7 +423,7 @@ public boolean exists(@NonNull String path) throws IOException {
}, listener)) {
listener.getLogger().println("Met criteria");
SCMHead head = new SCMHead(childPath);
observer.observe(head, new SCMRevisionImpl(head, svnEntry.getRevision()));
observer.observe(head, new SCMRevisionImpl(head, svnEntry.getRevision(), (additionalCredentials.size()>0)));
if (!observer.isObserving()) {
return;
}
Expand Down Expand Up @@ -689,9 +703,10 @@ public SubversionSCM build(@NonNull SCMHead head,
// name contains an @ so need to ensure there is an @ at the end of the name
remote.append('@');
}
return new SubversionSCM(remote.toString(), credentialsId, ".");
return new SubversionSCM(remote.toString(), credentialsId, ".", additionalCredentials);
}


/**
* Our implementation.
*/
Expand All @@ -701,16 +716,29 @@ public static class SCMRevisionImpl extends SCMRevision {
* The subversion revision.
*/
private long revision;
/**
* Indicates whether externals are used. If so, revision is considered non-deterministic.
*/
private boolean usesExternals;

public SCMRevisionImpl(SCMHead head, long revision) {
public SCMRevisionImpl(SCMHead head, long revision, boolean usesExternals) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breaking change, the class was public

super(head);
this.revision = revision;
this.usesExternals = usesExternals;
}

public long getRevision() {
return revision;
}

public boolean isDeterministic() {
//non-deterministic when using externals, to force Jenkins to recurse into subdirectories
if(usesExternals) {
return false;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nondeterministic revisions are acceptable only for SCMs which inherently cannot support determinism, like CVS I suppose (though even there -D timestamps may offer a solution). In the case of Subversion with externals, we would rather need to extend SCMRevisionImpl to encode the revision number in each external encountered.

}
return true;
}

/**
* {@inheritDoc}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@
<f:entry title="${%Exclude branches}" field="excludes">
<f:textbox default="${descriptor.DEFAULT_EXCLUDES}"/>
</f:entry>
<f:entry title="${%Additional Credentials}" field="additionalCredentials" help="/descriptor/hudson.scm.SubversionSCM/help/additionalCredentials">
<f:repeatableProperty field="additionalCredentials" add="${%Add additional credentials...}"/>
</f:entry>
</j:jelly>