diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..492a40b --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,4 @@ +_extends: .github +version-template: $MAJOR.$MINOR.$PATCH +tag-template: repo-$NEXT_PATCH_VERSION +name-template: Repo SCM Plugin $NEXT_PATCH_VERSION diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 0000000..e0419f2 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,17 @@ +# Note: additional setup is required, see https://github.com/jenkinsci/.github/blob/master/.github/release-drafter.adoc + +name: Release Drafter + +on: + push: + branches: + - "master" + +jobs: + update_release_draft: + runs-on: ubuntu-latest + steps: + # Drafts your next Release notes as Pull Requests are merged into the default branch + - uses: release-drafter/release-drafter@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 34b3584..ae06f45 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ work .classpath .project .settings +*.iml +.idea diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..f41551e --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,4 @@ +buildPlugin(useContainerAgent: true, timeout: 180, configurations: [ + [platform: 'linux', jdk: 21], + [platform: 'linux', jdk: 17], +]) diff --git a/KNOWN_BUGS.txt b/KNOWN_BUGS.txt new file mode 100644 index 0000000..ced75d0 --- /dev/null +++ b/KNOWN_BUGS.txt @@ -0,0 +1,5 @@ +Known bugs +========== + +Manually building a project with no changes, will cause a +"failed to determine" changes in the build result. diff --git a/LICENSE.txt b/LICENSE.txt index 28a4c19..bc36cca 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,5 +1,6 @@ The MIT License +Copyright (c) 2012-, Bjarke Freund-Hansen Copyright (c) 2011-, Brad Larson Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..f43478b --- /dev/null +++ b/README.adoc @@ -0,0 +1,210 @@ +[[Repo-plugin]] += Repo Plugin + +image:https://img.shields.io/jenkins/plugin/v/repo.svg[link="https://plugins.jenkins.io/repo"] +image:https://img.shields.io/github/release/jenkinsci/repo-plugin.svg?label=changelog[link="https://github.com/jenkinsci/repo-plugin/releases/latest"] +image:https://img.shields.io/jenkins/plugin/i/repo.svg?color=blue[link="https://plugins.jenkins.io/repo"] + +This plugin adds https://gerrit.googlesource.com/git-repo[Repo] as an SCM provider in Jenkins. + +*This plugin is up for adoption.* Want to help improve this plugin? +https://wiki.jenkins.io/display/JENKINS/Adopt+a+Plugin[Click here to +learn more]! + +:toc: preamble +:toclevels: 3 + +[[RepoPlugin-Description]] +== Description + +This plugin adds Repo (https://gerrit.googlesource.com/git-repo) as an SCM +provider for Jenkins. Projects can use this plugin to only run builds +when changes are detected in any of the git repositories in the repo +manifest, to list the changes between builds, and to re-create the +project state across all repositories for any previous build using a +static manifest. + +[[RepoPlugin-Pipelines]] +== Pipelines + +The repo plugin provides an SCM implementation to be used with the Pipeline SCM link:https://www.jenkins.io/doc/pipeline/steps/workflow-scm-step/[`checkout` step]. + +The link:https://www.jenkins.io/redirect/pipeline-snippet-generator[Pipeline Syntax Snippet Generator] guides the user to select checkout options. + +[[RepoPlugin-EnvironmentVariables]] +=== Environment Variables + +The repo plugin assigns values to environment variables in Pipeline projects. + +REPO_MANIFEST_URL:: URL of manifest repository used. +REPO_MANIFEST_BRANCH:: Branch of the manifest repository used. +REPO_MANIFEST_FILE:: Manifest filename used. +REPO_MANIFEST_XML:: Static manifest (in XML format). + +[[RepoPlugin-Changelog]] +== Changelog + +As of version 1.11.0 the changelog is moved to https://github.com/jenkinsci/repo-plugin/releases/[GitHub Releases] + +[[RepoPlugin-Version1.10.7-Mar3,2017]] +=== Version 1.10.7 - Mar 3, 2017 + +* Update URLs to valid locations in help html. +(https://github.com/jenkinsci/repo-plugin/pull/43[pull #43]) +* Support for evaluating $\{param} in destination dir. +(https://github.com/jenkinsci/repo-plugin/pull/44[pull #44]) +* Fix --force-sync help description. +(https://github.com/jenkinsci/repo-plugin/pull/45[pull #45]) + +[[RepoPlugin-Version1.10.6-Jan10,2017]] +=== Version 1.10.6 - Jan 10, 2017 + +* Use local_manifests/local.xml rather than local_manifest.xml. +(https://github.com/jenkinsci/repo-plugin/pull/42[pull #42]) + +[[RepoPlugin-Version1.10.5-Nov30,2016]] +=== Version 1.10.5 - Nov 30, 2016 + +* https://issues.jenkins-ci.org/browse/JENKINS-40114[JENKINS-40114] +Fixed. (https://github.com/jenkinsci/repo-plugin/pull/41[pull #41]) + +[[RepoPlugin-Version1.10.4-Nov29,2016]] +=== Version 1.10.4 - Nov 29, 2016 + +* Fixex typos in local manifest help. +(https://github.com/jenkinsci/repo-plugin/pull/37[pull #37]) +* https://issues.jenkins-ci.org/browse/JENKINS-36703[JENKINS-36703] +Fixed polling behaviour. +(https://github.com/jenkinsci/repo-plugin/pull/38[pull #38]) +* Fixed some repo commands. +(https://github.com/jenkinsci/repo-plugin/pull/39[pull #39]) + +[[RepoPlugin-Version1.10.3-Aug18,2016]] +=== Version 1.10.3 - Aug 18, 2016 + +* https://issues.jenkins-ci.org/browse/JENKINS-37416[JENKINS-37416] +Expand local manifest. +(https://github.com/jenkinsci/repo-plugin/pull/36[pull #36]) + +[[RepoPlugin-Version1.10.2-Jul13,2016]] +=== Version 1.10.2 - Jul 13, 2016 + +* https://issues.jenkins-ci.org/browse/JENKINS-36644[JENKINS-36644] Fix +Tag action is not working in pipeline job. +(https://github.com/jenkinsci/repo-plugin/pull/35[pull #35]) +* https://issues.jenkins-ci.org/browse/JENKINS-33958[JENKINS-33958] Fix +changelog hang when used with pipeline. +(https://github.com/jenkinsci/repo-plugin/pull/34[pull #34]) + +[[RepoPlugin-Version1.10.1-Jul11,2016]] +=== Version 1.10.1 - Jul 11, 2016 + +* https://issues.jenkins-ci.org/browse/JENKINS-14539[JENKINS-14539] Fix +issue with Email Ext plugin - full name was returned instead of email. +(https://github.com/jenkinsci/repo-plugin/pull/33[pull #33]) + +[[RepoPlugin-Version1.10.0-Feb22,2015]] +=== Version 1.10.0 - Feb 22, 2015 + +* Adding an option to ignore specific projects on scm poll. +(https://github.com/jenkinsci/repo-plugin/pull/31[pull #31]) + +[[RepoPlugin-Version1.9.0-Jan21,2015]] +=== Version 1.9.0 - Jan 21, 2015 + +* Support for +https://wiki.jenkins.io/display/JENKINS/Pipeline+Plugin[Pipeline Plugin] +(https://github.com/jenkinsci/repo-plugin/pull/28[pull #28]) + +[[RepoPlugin-Version1.8.0-Sept25th,2015]] +=== Version 1.8.0 - Sept 25th, 2015 + +* --force-sync (https://github.com/jenkinsci/repo-plugin/pull/26[pull +#26]) +* --no-tags (https://github.com/jenkinsci/repo-plugin/pull/27[pull #27]) + +[[RepoPlugin-Version1.7.1-May6th,2015]] +=== Version 1.7.1 - May 6th, 2015 + +* Fix some options can't be shown properly in configuration page +(https://github.com/jenkinsci/repo-plugin/pull/25[pull #25]) + +[[RepoPlugin-Version1.7-Apr23rd,2015]] +=== Version 1.7 - Apr 23rd, 2015 + +* Support for shallow clones, option to reset the repo before syncing +(https://github.com/jenkinsci/repo-plugin/pull/20[pull #20]) +* Fixed +https://issues.jenkins-ci.org/browse/JENKINS-17913[JENKINS-17913] Expand +manifest file and URL. +(https://github.com/jenkinsci/repo-plugin/pull/21[pull #21]) +* Added --trace option. +(https://github.com/jenkinsci/repo-plugin/pull/22[pull #22]) +* Fixed +https://issues.jenkins-ci.org/browse/JENKINS-23262[JENKINS-23262] +(https://github.com/jenkinsci/repo-plugin/pull/22[pull #22]) +* Added option for --first-parent in changelog. +(https://github.com/jenkinsci/repo-plugin/pull/23[pull #23]) + +[[RepoPlugin-Version1.6-Nov19th,2013]] +=== Version 1.6 - Nov 19th, 2013 + +* Allow parameters in repo branch name +(https://issues.jenkins-ci.org/browse/JENKINS-17913[issue #20]) +* Fixed a bug where a poll compared the current workspace and polled +branch incorrectly. +* Improved git log + +[[RepoPlugin-Version1.5-April23th,2013]] +=== Version 1.5 - April 23th, 2013 + +* Support for repo init -g +* Support for repo init --repo-url +* Parent pom updated to jenkins 1.424 + +[[RepoPlugin-Version1.3-November19th,2012]] +=== Version 1.3 - November 19th, 2012 + +* Lowered memory footprint in case of projects with a large build +history. +* Support repo options '-c' and '-q'. +* Fix: Repo does not implement +getAffectedFiles() (https://issues.jenkins-ci.org/browse/JENKINS-14926[issue +#14926]). +* Allow localManifest to be specified either literally or as an URL. + +[[RepoPlugin-Version1.2.1-April23rd,2012]] +=== Version 1.2.1 - April 23rd, 2012 + +* Fix : Jobs using repo plugin do not persist +(https://issues.jenkins-ci.org/browse/JENKINS-12466[JENKINS-12466]) +* Fix : Fixed NPE in RevisionState.hashCode() + +[[RepoPlugin-Version1.2]] +=== Version 1.2 + +If build scripts modify the workspace, which cause problems during repo +sync, try running git reset --hard on the repository and re-running repo +sync. Thanks to https://github.com/tgover1[tgover]. + +Don't show all the changes brought in from a merge commit in the change +log, just show the merge commit (see git log --first-parent). This fixes +a problem of a merge commit breaking the build and all authors of +changes brought in with that merge commit getting emailed about it. +Thanks to https://github.com/tgover1[tgover]. + +[[RepoPlugin-Version1.1]] +=== Version 1.1 + +Add support for syncing from local mirrors, specify the number of +projects to sync simultaneously, use a local manifest, and sync to a +subdirectory of the workspace. Thanks to +https://github.com/tgover1[tgover]. + +Add support to specify the name of the manifest file to use. Thanks to +https://github.com/farshidce[farshidce]. + +[[RepoPlugin-Version1.0]] +=== Version 1.0 + +Initial Release \ No newline at end of file diff --git a/README.txt b/README.txt deleted file mode 100644 index 3429d66..0000000 --- a/README.txt +++ /dev/null @@ -1,12 +0,0 @@ -This plugin adds Repo (http://code.google.com/p/git-repo/) as an SCM -provider for Jenkins. Projects can use this plugin to only run builds -when changes are detected in any of the git repositories in the repo -manifest, to list the changes between builds, and to re-create the -project state across all repositories for any previous build using -a static manifest. - -============= - -Maintainer - -Brad Larson - bklarson@gmail.com diff --git a/ROADMAP b/ROADMAP index ca7d05f..41f5f4a 100644 --- a/ROADMAP +++ b/ROADMAP @@ -1,10 +1,15 @@ -Future features roadmap (Mostly from Sony Ericson) +Future features roadmap +======================= Roughly in order of priority. I have no solid plans to work on any of these, but if you'd like to make any contributions, this list is a good place to start! Investigate usage of ToolInstaller + Automagically fetch repo from a URL, put it in the workspace and + put that dir in the PATH for the current build. Such that it is + not necessary to install repo manually on all your slaves. + Wipe workspace option? (Follow git plugin here) Badge/Tag support @@ -12,3 +17,4 @@ Gerrit-Download integration (Gerrit Trigger plugin support) Better auto-retry logic Support smartsync (-s option) + diff --git a/checkstyle.xml b/checkstyle.xml index 785f8a6..973aab0 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -6,7 +6,7 @@ --> @@ -41,10 +41,15 @@ Checkstyle configuration that checks coding conventions. - - + + + + + + + @@ -66,7 +71,7 @@ Checkstyle configuration that checks coding conventions. - + @@ -80,9 +85,9 @@ Checkstyle configuration that checks coding conventions. - + @@ -120,6 +125,7 @@ Checkstyle configuration that checks coding conventions. + diff --git a/pom.xml b/pom.xml index 4774dfd..9fa5990 100644 --- a/pom.xml +++ b/pom.xml @@ -1,18 +1,27 @@ 4.0.0 - org.jvnet.hudson.plugins + org.jenkins-ci.plugins plugin - 1.350 + 5.17 + org.jenkins-ci.plugins repo - 1.3-SNAPSHOT + 1.17.1-SNAPSHOT hpi Jenkins REPO plugin Integrates Jenkins with REPO SCM - http://wiki.jenkins-ci.org/display/JENKINS/Repo+Plugin + https://github.com/jenkinsci/repo-plugin/blob/master/README.adoc + + + 1.15.1 + -SNAPSHOT + 2.492.3 + UTF-8 + UTF-8 + @@ -22,42 +31,32 @@ - - - bklarson - Brad Larson - bklarson@gmail.com - - developer - maintainer - - -6 - - - - - org.apache.maven.plugins - maven-release-plugin - - deploy - - org.apache.maven.plugins maven-checkstyle-plugin - 2.6 + 2.17 checkstyle.xml true true - - check - compile - + + compile-checkstyle + + checkstyle + + compile + + + test-check + + check + + test + @@ -71,32 +70,37 @@ - - - UTF-8 - UTF-8 - - - junit - junit - 4.8.1 - test - + maven.jenkins-ci.org - http://maven.jenkins-ci.org:8081/content/repositories/releases/ + https://repo.jenkins-ci.org/releases - + - scm:git:git://github.com/jenkinsci/repo-plugin.git + scm:git:https://github.com/jenkinsci/repo-plugin.git scm:git:git@github.com:jenkinsci/repo-plugin.git http://github.com/jenkinsci/repo-plugin + HEAD + + + + repo.jenkins-ci.org + https://repo.jenkins-ci.org/public/ + + + + + + repo.jenkins-ci.org + https://repo.jenkins-ci.org/public/ + + diff --git a/src/main/java/hudson/plugins/repo/ChangeLog.java b/src/main/java/hudson/plugins/repo/ChangeLog.java index 261094f..74eb430 100644 --- a/src/main/java/hudson/plugins/repo/ChangeLog.java +++ b/src/main/java/hudson/plugins/repo/ChangeLog.java @@ -25,11 +25,12 @@ import hudson.FilePath; import hudson.Launcher; -import hudson.model.AbstractBuild; +import hudson.model.Run; import hudson.plugins.repo.ChangeLogEntry.ModifiedFile; import hudson.scm.ChangeLogParser; +import hudson.scm.EditType; +import hudson.scm.RepositoryBrowser; import hudson.util.AtomicFileWriter; -import hudson.util.IOException2; import hudson.util.XStream2; import java.io.BufferedReader; @@ -38,9 +39,10 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; -import java.io.OutputStream; import java.io.Reader; +import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -49,11 +51,14 @@ import com.thoughtworks.xstream.io.StreamException; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + /** * Utility functions to generate and parse a file listing the differences * between builds. Differences are saved as a list of ChangeLogEntry. */ -public class ChangeLog extends ChangeLogParser { +class ChangeLog extends ChangeLogParser { private static Logger debug = Logger.getLogger("hudson.plugins.repo.ChangeLog"); @@ -62,11 +67,11 @@ public class ChangeLog extends ChangeLogParser { // require creating git commits, which will be tricky. See the git plugin // for some possibilities. - @SuppressWarnings("unchecked") @Override + @SuppressWarnings("unchecked") public RepoChangeLogSet parse( - @SuppressWarnings("rawtypes") final AbstractBuild build, - final File changelogFile) throws IOException, SAXException { + final Run build, final RepositoryBrowser browser, final File changelogFile) + throws IOException, SAXException { final List r; final XStream2 xs = new XStream2(); final Reader reader = @@ -79,7 +84,7 @@ public RepoChangeLogSet parse( reader.close(); } - return new RepoChangeLogSet(build, r); + return new RepoChangeLogSet(build, browser, r); } /** @@ -95,16 +100,19 @@ public RepoChangeLogSet parse( * @param workspace * The FilePath of the workspace to use when computing * differences. This path might be on a slave machine. + * @param showAllChanges + * Add --first-parent to "git log" * @throws IOException * is thrown if we have problems writing to the changelogFile * @throws InterruptedException * is thrown if we are interrupted while waiting on the git * commands to run in a forked process. */ - public static List generateChangeLog( - final RevisionState currentState, - final RevisionState previousState, final Launcher launcher, - final FilePath workspace) throws IOException, + private static List generateChangeLog( + @Nonnull final RevisionState currentState, + @Nullable final RevisionState previousState, final Launcher launcher, + final FilePath workspace, final boolean showAllChanges) + throws IOException, InterruptedException { final List changes = currentState.whatChanged(previousState); @@ -112,22 +120,21 @@ public static List generateChangeLog( debug.log(Level.FINEST, "generateChangeLog: changes " + changes); if (changes == null || changes.size() == 0) { // No changes or the first job - return null; + return Collections.emptyList(); } final List commands = new ArrayList(5); final List logs = new ArrayList(); - for (final ProjectState change : changes) { debug.log(Level.FINEST, "change: " + change); + String newRevision = currentState.getRevision(change.getPath()); if (change.getRevision() == null) { // This project was just added to the manifest. logs.add(new ChangeLogEntry(change.getPath(), change - .getServerPath(), null, null, null, null, null, null, + .getServerPath(), newRevision, null, null, null, null, null, null, "This project was added to the manifest.", null)); continue; } - String newRevision = currentState.getRevision(change.getPath()); if (newRevision == null) { // This project was just removed from the manifest. logs.add(new ChangeLogEntry(change.getPath(), change @@ -141,9 +148,22 @@ public static List generateChangeLog( commands.add("git"); commands.add("log"); commands.add("--raw"); - commands.add("--first-parent"); - commands.add("--format=\"zzREPOzz%H%n%an<%ae>%aD" - + "%n%cn<%ce>%cD%n%s%n%n%byyREPOyy\""); + if (!showAllChanges) { + commands.add("--first-parent"); + } + + final String format = "[[]]" + + "%H[[]" + + "%an[[]" + + "%ae[[]" + + "%aD[[]" + + "%cn[[]" + + "%ce[[]" + + "%cD[[]" + + "%s\n%b[[]"; + + + commands.add("--format=\"" + format + "\""); // TODO: make this work with the -M flag to show copied and renamed // files. // TODO: even better, use jgit to do the diff. It would be faster, @@ -151,51 +171,90 @@ public static List generateChangeLog( // is definitely preferable. Most of the code can probably be copied // from Gerrit. It might be tricky with master/slave setup. commands.add(change.getRevision() + ".." + newRevision); - final OutputStream gitOutput = new ByteArrayOutputStream(); - launcher.launch().stdout(gitOutput).pwd(gitdir).cmds(commands) - .join(); - final String[] changelogs = - gitOutput.toString().split("zzREPOzz"); + final ByteArrayOutputStream gitOutput = new ByteArrayOutputStream(); + if (launcher.launch().stdout(gitOutput).pwd(gitdir).cmds(commands) + .join() != 0) { + commands.remove(commands.size() - 1); + commands.add("HEAD"); + launcher.launch().stdout(gitOutput).pwd(gitdir).cmds(commands) + .join(); + } + final String o = new String(gitOutput.toByteArray(), Charset.defaultCharset()); + final String[] changelogs = o.split( + "\\[\\[\\]\\]"); + debug.log(Level.INFO, o); for (final String changelog : changelogs) { - if (changelog.length() < 10) { - // This isn't a helpful message. Skip it. + final String[] parts = changelog.split( + "\\[\\[\\]"); + if (parts.length < 9) { + // this is broken continue; } - int endLine = changelog.indexOf('\n'); - final String revision = changelog.substring(0, endLine); - int firstEmailPos = changelog.indexOf('<', endLine); - final String authorName = - changelog.substring(endLine + 1, firstEmailPos); - int endEmail = changelog.indexOf('>', firstEmailPos); - final String authorEmail = - changelog.substring(firstEmailPos + 1, endEmail); - endLine = changelog.indexOf('\n', endEmail); - final String authorDate = - changelog.substring(endEmail + 1, endLine); - firstEmailPos = changelog.indexOf('<', endLine); - final String committerName = - changelog.substring(endLine + 1, firstEmailPos); - endEmail = changelog.indexOf('>', firstEmailPos); - final String committerEmail = - changelog.substring(firstEmailPos + 1, endEmail); - endLine = changelog.indexOf('\n', endEmail); - final String committerDate = - changelog.substring(endEmail + 1, endLine); - final int endComment = changelog.indexOf("yyREPOyy", endLine); - final String commitText = - changelog.substring(endLine + 1, endComment); + final String revision = parts[0]; + final String authorName = parts[1]; + final String authorEmail = parts[2]; + final String authorDate = parts[3]; + final String committerName = parts[4]; + final String committerEmail = parts[5]; + final String committerDate = parts[6]; + final String commitText = parts[7]; + final String[] fileLines = parts[8].split("\n"); - final String[] fileLines = - changelog.substring(endComment).split("\n"); final List modifiedFiles = new ArrayList(); for (final String fileLine : fileLines) { + // Format of these lines is described in the "RAW OUTPUT + // FORMAT" section of "git diff --help"... + // + // An output line is formatted this way: + // in-place edit :100644 100644 bcd1234... 0123456... M file0 + // copy-edit :100644 100644 abcd123... 1234567... C68 file1 file2 + // rename-edit :100644 100644 abcd123... 1234567... R86 file1 file3 + // create :000000 100644 0000000... 1234567... A file4 + // delete :100644 000000 1234567... 0000000... D file5 + // unmerged :000000 000000 0000000... 0000000... U file6 + // + // Note that the filenames are preceded by tabs rather than spaces. + if (!fileLine.startsWith(":")) { continue; } - final char action = fileLine.substring(37, 38).charAt(0); - final String path = fileLine.substring(39); - modifiedFiles.add(new ModifiedFile(path, action)); + + final String[] spaceParts = fileLine.split(" ", 5); + if (spaceParts.length != 5) { + continue; + } + + final String[] tabParts = spaceParts[4].split("\t"); + if (tabParts[0].isEmpty()) { + continue; + } + final char action = tabParts[0].charAt(0); + final int expectedLen = ((action == 'C') || (action == 'R')) ? 3 : 2; + if (tabParts.length != expectedLen) { + continue; + } + + switch (action) { + case 'M': + modifiedFiles.add(new ModifiedFile(tabParts[1], EditType.EDIT)); + break; + case 'C': + modifiedFiles.add(new ModifiedFile(tabParts[2], EditType.ADD)); + break; + case 'R': + modifiedFiles.add(new ModifiedFile(tabParts[1], EditType.DELETE)); + modifiedFiles.add(new ModifiedFile(tabParts[2], EditType.ADD)); + break; + case 'A': + modifiedFiles.add(new ModifiedFile(tabParts[1], EditType.ADD)); + break; + case 'D': + modifiedFiles.add(new ModifiedFile(tabParts[1], EditType.DELETE)); + break; + default: + continue; + } } ChangeLogEntry nc = new ChangeLogEntry(change.getPath(), change .getServerPath(), revision, authorName, authorEmail, @@ -226,19 +285,22 @@ public static List generateChangeLog( * @param workspace * The FilePath of the workspace to use when computing * differences. This path might be on a slave machine. + * @param showAllChanges + * Add --first-parent to "git log" * @throws IOException * is thrown if we have problems writing to the changelogFile * @throws InterruptedException * is thrown if we are interrupted while waiting on the git * commands to run in a forked process. */ - public static void saveChangeLog(final RevisionState currentState, - final RevisionState previousState, final File changelogFile, - final Launcher launcher, final FilePath workspace) + static void saveChangeLog(@Nonnull final RevisionState currentState, + @Nullable final RevisionState previousState, final File changelogFile, + final Launcher launcher, final FilePath workspace, + final boolean showAllChanges) throws IOException, InterruptedException { List logs = generateChangeLog(currentState, previousState, launcher, - workspace); + workspace, showAllChanges); if (logs == null) { debug.info("No logs found"); @@ -252,7 +314,7 @@ public static void saveChangeLog(final RevisionState currentState, xs.toXML(logs, w); w.commit(); } catch (final StreamException e) { - throw new IOException2(e); + throw new IOException("Could not save changelog", e); } finally { w.close(); } diff --git a/src/main/java/hudson/plugins/repo/ChangeLogEntry.java b/src/main/java/hudson/plugins/repo/ChangeLogEntry.java index 8743214..76e9179 100644 --- a/src/main/java/hudson/plugins/repo/ChangeLogEntry.java +++ b/src/main/java/hudson/plugins/repo/ChangeLogEntry.java @@ -43,31 +43,22 @@ public class ChangeLogEntry extends ChangeLogSet.Entry { * contains a list of ModifiedFiles. We track the file path and how it was * modified (added, edited, removed, etc). */ - public static class ModifiedFile implements AffectedFile { - - /** - * An EditType for a Renamed file. Most version control systems don't - * support file renames, so this EditType isn't in the default set - * provided by Hudson. - */ - public static final EditType RENAME = new EditType("rename", - "The file was renamed"); + static class ModifiedFile implements AffectedFile { private final String path; - private final char action; + private final EditType editType; /** - * Create a new ModifiedFile object with the given path and action. + * Create a new ModifiedFile object with the given path and edit type. * * @param path * the path of the file - * @param action - * the action performed on the file, as reported by Git (A - * for add, D for delete, M for modified, etc) + * @param editType + * edit type */ - public ModifiedFile(final String path, final char action) { + ModifiedFile(final String path, final EditType editType) { this.path = path; - this.action = action; + this.editType = editType; } /** @@ -78,28 +69,10 @@ public String getPath() { } /** - * Returns the action performed on the file. - */ - public char getAction() { - return action; - } - - /** - * Returns the EditType performed on the file (based on the action). + * Returns the EditType performed on the file. */ public EditType getEditType() { - if (action == 'A') { - return EditType.ADD; - } else if (action == 'D') { - return EditType.DELETE; - } else if (action == 'M') { - return EditType.EDIT; - } else if (action == 'R') { - return RENAME; - } else { - return new EditType("unknown: " + action, - "An unknown file action"); - } + return editType; } } @@ -260,6 +233,15 @@ public List getModifiedFiles() { return modifiedFiles; } + /** + * Returns a set of paths in the workspace that was + * affected by this change. + */ + @Override + public List getAffectedFiles() { + return modifiedFiles; + } + @Override public String getMsg() { return getCommitText(); @@ -270,7 +252,7 @@ public User getAuthor() { if (authorName == null) { return User.getUnknown(); } - return User.get(authorName); + return User.get(authorEmail); } @Override @@ -285,6 +267,9 @@ public void setParent( @Override public Collection getAffectedPaths() { + if (modifiedFiles == null) { + return null; + } return new AbstractList() { @Override public String get(final int index) { diff --git a/src/main/java/hudson/plugins/repo/ManifestAction.java b/src/main/java/hudson/plugins/repo/ManifestAction.java new file mode 100644 index 0000000..512f635 --- /dev/null +++ b/src/main/java/hudson/plugins/repo/ManifestAction.java @@ -0,0 +1,109 @@ +/* + * The MIT License + * + * Copyright (c) 2010, Brad Larson + * + * 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 hudson.plugins.repo; + +import hudson.model.BuildBadgeAction; +import hudson.model.Run; +import jenkins.model.RunAction2; +import org.kohsuke.stapler.export.ExportedBean; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +/** + * A Manifest Action displays the static manifest information needed + * to recreate the exact state of the repository when the build was run. + */ +@ExportedBean(defaultVisibility = 999) +public class ManifestAction implements RunAction2, Serializable, BuildBadgeAction { + private static Logger debug = Logger + .getLogger("hudson.plugins.repo.ManifestAction"); + private static final long serialVersionUID = 1; + + private transient Run run; + + /** + * Constructs the manifest action object. + * @param run Build whose manifest we wish to display. + */ + ManifestAction(final Run run) { + this.run = run; + } + + @Override + public void onAttached(final Run r) { + this.run = r; + } + + @Override + public void onLoad(final Run r) { + this.run = r; + } + + /** + * Getter for the run property. + */ + public Run getRun() { + return run; + } + + /** + * Returns the filename to use as the badge. + */ + public String getIconFileName() { + return "star"; + } + + /** + * Returns the display name to use for the action. + */ + public String getDisplayName() { + return "Repo Manifest"; + } + + /** + * Returns the name of the Url to use for the action. + */ + public final String getUrlName() { + return "repo-manifest"; + } + + /** + * Gets a String representation of the static manifest for this repo snapshot. + */ + public List getManifests() { + List manifests = new ArrayList(); + + final List revisionStates = getRun().getActions(RevisionState.class); + if (revisionStates != null) { + for (RevisionState revisionState : revisionStates) { + manifests.add(new StaticManifest(revisionState.getFile(), revisionState.getBranch(), + revisionState.getUrl(), revisionState.getManifest())); + } + } + return manifests; + } +} diff --git a/src/main/java/hudson/plugins/repo/ManifestValidator.java b/src/main/java/hudson/plugins/repo/ManifestValidator.java new file mode 100644 index 0000000..093dc0d --- /dev/null +++ b/src/main/java/hudson/plugins/repo/ManifestValidator.java @@ -0,0 +1,83 @@ +/* + * The MIT License + * + * Copyright (c) 2010, Brad Larson + * + * 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 hudson.plugins.repo; + +import hudson.AbortException; +import jenkins.util.xml.XMLUtils; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.NodeList; + +import org.xml.sax.SAXException; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Locale; + +/** + * to validate manifest xml file and abort when remote references a local path. + */ +public final class ManifestValidator { + + private ManifestValidator() { + // to hide the implicit public constructor + } + + /** + * to validate manifest xml file and abort when remote references a local path. + * @param manifestText byte representation of manifest file + * @param manifestRepositoryUrl url + * @throws IOException when remote references a local path. + */ + public static void validate(final byte[] manifestText, final String manifestRepositoryUrl) + throws IOException { + if (manifestText.length > 0) { + try { + Document doc = XMLUtils.parse(new ByteArrayInputStream(manifestText)); + NodeList remote = doc.getElementsByTagName("remote"); + for (int i = 0; i < remote.getLength(); i++) { + NamedNodeMap attributes = remote.item(i).getAttributes(); + for (int j = 0; j < attributes.getLength(); j++) { + if ("fetch".equals(attributes.item(j).getNodeName()) + && attributes.item(j).getNodeValue() + .toLowerCase(Locale.ENGLISH).startsWith("file://")) { + // we don't need to check source using Files.exists because fetch + // attribute could resolve only local paths starting from 'file://' + throw new AbortException("Checkout of Repo url '" + + manifestRepositoryUrl + + "' aborted because manifest references a local " + + "directory, which may be insecure. You can allow " + + "local checkouts anyway" + + " by setting the system property '" + + RepoScm.ALLOW_LOCAL_CHECKOUT_PROPERTY + "' to true."); + } + } + } + } catch (SAXException e) { + throw new IOException("Could not validate manifest"); + } + } + } +} diff --git a/src/main/java/hudson/plugins/repo/ProjectState.java b/src/main/java/hudson/plugins/repo/ProjectState.java index 4b6af2e..861dde4 100644 --- a/src/main/java/hudson/plugins/repo/ProjectState.java +++ b/src/main/java/hudson/plugins/repo/ProjectState.java @@ -23,6 +23,9 @@ */ package hudson.plugins.repo; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -31,7 +34,7 @@ * when projects have changed. A repo manifest contains a list of projects, and * a build in Hudson has a list of ProjectStates. */ -public class ProjectState { +final class ProjectState implements Serializable { private final String path; private final String serverPath; @@ -40,9 +43,14 @@ public class ProjectState { private static Logger debug = Logger.getLogger("hudson.plugins.repo.ProjectState"); + private static Map projectStateCache + = new HashMap(); + /** * Create an object representing the state of a project. * + * Project state is immutable and cached. + * * @param path * The client-side path of the project * @param serverPath @@ -50,7 +58,25 @@ public class ProjectState { * @param revision * The SHA-1 revision of the project */ - public ProjectState(final String path, final String serverPath, + static synchronized ProjectState constructCachedInstance( + final String path, final String serverPath, final String revision) { + ProjectState projectState + = projectStateCache.get( + calculateHashCode(path, serverPath, revision)); + + if (projectState == null) { + projectState = new ProjectState(path, serverPath, revision); + projectStateCache.put(projectState.hashCode(), projectState); + } + + return projectState; + } + + /** + * Private constructor called by named constructor + * constructCachedInstance(). + */ + private ProjectState(final String path, final String serverPath, final String revision) { this.path = path; this.serverPath = serverPath; @@ -60,6 +86,24 @@ public ProjectState(final String path, final String serverPath, + " revision: " + revision); } + /** + * Enforce usage of the cache when xstream deserializes the + * ProjectState objects. + */ + private synchronized Object readResolve() { + ProjectState projectState + = projectStateCache.get( + calculateHashCode(path, serverPath, revision)); + + if (projectState == null) { + projectStateCache.put(this.hashCode(), this); + projectState = this; + } + + return projectState; + } + + /** * Gets the client-side path of the project. */ @@ -99,8 +143,24 @@ public boolean equals(final Object obj) { @Override public int hashCode() { + return calculateHashCode(path, serverPath, revision); + } + + /** + * Calculates the hash code of a would-be ProjectState object with + * the provided parameters. + * + * @param path + * The client-side path of the project + * @param serverPath + * The server-side path of the project + * @param revision + * The SHA-1 revision of the project + */ + private static int calculateHashCode(final String path, + final String serverPath, final String revision) { return 23 + (path == null ? 37 : path.hashCode()) - + (serverPath == null ? 97 : serverPath.hashCode()) - + (revision == null ? 389 : revision.hashCode()); + + (serverPath == null ? 97 : serverPath.hashCode()) + + (revision == null ? 389 : revision.hashCode()); } } diff --git a/src/main/java/hudson/plugins/repo/RepoChangeLogSet.java b/src/main/java/hudson/plugins/repo/RepoChangeLogSet.java index 5f8d65a..9e92122 100644 --- a/src/main/java/hudson/plugins/repo/RepoChangeLogSet.java +++ b/src/main/java/hudson/plugins/repo/RepoChangeLogSet.java @@ -23,8 +23,9 @@ */ package hudson.plugins.repo; -import hudson.model.AbstractBuild; +import hudson.model.Run; import hudson.scm.ChangeLogSet; +import hudson.scm.RepositoryBrowser; import java.util.Iterator; import java.util.List; @@ -33,7 +34,7 @@ * A ChangeLogSet, which is used when generating the list of changes from one * build to the next. */ -public class RepoChangeLogSet extends ChangeLogSet { +class RepoChangeLogSet extends ChangeLogSet { private final List logs; /** @@ -42,13 +43,15 @@ public class RepoChangeLogSet extends ChangeLogSet { * * @param build * The build which caused this change log. + * @param browser + * Repository browser. * @param logs * a list of RepoChangeLogEntry, containing every change (commit) * which has occurred since the last build. */ - protected RepoChangeLogSet(final AbstractBuild build, - final List logs) { - super(build); + RepoChangeLogSet(final Run build, + final RepositoryBrowser browser, final List logs) { + super(build, browser); this.logs = logs; for (final ChangeLogEntry log : logs) { log.setParent(this); diff --git a/src/main/java/hudson/plugins/repo/RepoScm.java b/src/main/java/hudson/plugins/repo/RepoScm.java index 42d66cf..cc767e1 100644 --- a/src/main/java/hudson/plugins/repo/RepoScm.java +++ b/src/main/java/hudson/plugins/repo/RepoScm.java @@ -23,61 +23,122 @@ */ package hudson.plugins.repo; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Serializable; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import hudson.AbortException; +import hudson.EnvVars; import hudson.Extension; import hudson.FilePath; import hudson.Launcher; import hudson.Util; -import hudson.model.BuildListener; -import hudson.model.TaskListener; -import hudson.model.AbstractBuild; -import hudson.model.AbstractProject; +import hudson.model.Job; +import hudson.model.ParameterDefinition; +import hudson.model.ParametersDefinitionProperty; import hudson.model.Run; +import hudson.model.StringParameterDefinition; +import hudson.model.TaskListener; import hudson.scm.ChangeLogParser; import hudson.scm.PollingResult; import hudson.scm.SCM; import hudson.scm.SCMDescriptor; import hudson.scm.SCMRevisionState; -import hudson.scm.PollingResult.Change; -import hudson.util.FormValidation; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - import net.sf.json.JSONObject; +import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerRequest2; +import org.kohsuke.stapler.export.Exported; +import org.kohsuke.stapler.export.ExportedBean; + +import hudson.scm.PollingResult.Change; +import hudson.util.FormValidation; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * The main entrypoint of the plugin. This class contains code to store user * configuration and to check out the code using a repo binary. */ -public class RepoScm extends SCM { + +@ExportedBean +public class RepoScm extends SCM implements Serializable { + + /** + * maximum length of string passed to a single environment variable. + */ + private static final int MAX_ARG_STRLEN = 131072; private static Logger debug = Logger .getLogger("hudson.plugins.repo.RepoScm"); + /** + * escape hatch name. + */ + static final String ALLOW_LOCAL_CHECKOUT_PROPERTY = + RepoScm.class.getName() + ".ALLOW_LOCAL_CHECKOUT"; + /** + * escape hatch. + */ + //CS IGNORE VisibilityModifier FOR NEXT 1 LINES. REASON: escape hatch property. + static /* not final */ boolean ALLOW_LOCAL_CHECKOUT = Boolean.parseBoolean(System.getProperty(ALLOW_LOCAL_CHECKOUT_PROPERTY, "false")); + private final String manifestRepositoryUrl; // Advanced Fields: - private final String manifestBranch; - private final String manifestFile; - private final String repoUrl; - private final String mirrorDir; - private final int jobs; - private final String localManifest; - private final String destinationDir; + @CheckForNull private String manifestFile; + @CheckForNull private String manifestGroup; + @CheckForNull private String manifestPlatform; + @CheckForNull private String repoUrl; + @CheckForNull private String repoBranch; + @CheckForNull private String mirrorDir; + @CheckForNull private String manifestBranch; + @CheckForNull private int jobs; + @CheckForNull private int depth; + @CheckForNull private String localManifest; + @CheckForNull private String destinationDir; + @CheckForNull private boolean currentBranch; + @CheckForNull private boolean resetFirst; + @CheckForNull private boolean cleanFirst; + @CheckForNull private boolean quiet; + @CheckForNull private boolean forceSync; + @CheckForNull private boolean trace; + @CheckForNull private boolean showAllChanges; + @CheckForNull private boolean noTags; + @CheckForNull private boolean manifestSubmodules; + @CheckForNull private boolean fetchSubmodules; + @CheckForNull private Set ignoreProjects; + @CheckForNull private EnvVars extraEnvVars; + @CheckForNull private boolean noCloneBundle; + @CheckForNull private boolean worktree; + @CheckForNull private boolean noSync; + @CheckForNull private boolean gitLfs; /** * Returns the manifest repository URL. */ + @Exported public String getManifestRepositoryUrl() { return manifestRepositoryUrl; } @@ -86,22 +147,102 @@ public String getManifestRepositoryUrl() { * Returns the manifest branch name. By default, this is null and repo * defaults to "master". */ + @Exported public String getManifestBranch() { return manifestBranch; } + /** + * Merge the provided environment with the default values of + * the project parameters. The values from the provided environment + * take precedence. + * @param environment an existing environment, which contains already + * properties from the current build + * @param project the project that is being built + */ + private EnvVars getEnvVars(final EnvVars environment, + final Job project) { + // create an empty vars map + final EnvVars finalEnv = new EnvVars(); + final ParametersDefinitionProperty params = project.getProperty( + ParametersDefinitionProperty.class); + if (params != null) { + for (ParameterDefinition param + : params.getParameterDefinitions()) { + if (param instanceof StringParameterDefinition) { + final StringParameterDefinition stpd = + (StringParameterDefinition) param; + final String dflt = stpd.getDefaultValue(); + if (dflt != null) { + finalEnv.put(param.getName(), dflt); + } + } + } + } + // now merge the settings from the last build environment + if (environment != null) { + finalEnv.overrideAll(environment); + } + + // merge extra env vars, if specified + if (extraEnvVars != null) { + finalEnv.overrideAll(extraEnvVars); + } + + EnvVars.resolve(finalEnv); + return finalEnv; + } + /** * Returns the initial manifest file name. By default, this is null and repo * defaults to "default.xml" */ + @Exported public String getManifestFile() { return manifestFile; } + /** + * Returns the group of projects to fetch. By default, this is null and + * repo will fetch the default group. + */ + @Exported + public String getManifestGroup() { + return manifestGroup; + } + + /** + * Returns the platform of projects to fetch. By default, this is null and + * repo will automatically fetch the appropriate platform. + */ + @CheckForNull + public String getManifestPlatform() { + return manifestPlatform; + } + + /** + * Returns the repo url. by default, this is null and + * repo is fetched from aosp + */ + @Exported + public String getRepoUrl() { + return repoUrl; + } + + /** + * Returns the repo branch. by default, this is null and + * repo is used from the default branch + */ + @Exported + public String getRepoBranch() { + return repoBranch; + } + /** * Returns the name of the mirror directory. By default, this is null and * repo does not use a mirror. */ + @Exported public String getMirrorDir() { return mirrorDir; } @@ -110,14 +251,24 @@ public String getMirrorDir() { * Returns the number of jobs used for sync. By default, this is null and * repo does not use concurrent jobs. */ + @Exported public int getJobs() { return jobs; } /** - * Returns the contents of the local_manifest.xml. By default, this is null - * and a local_manifest.xml is neither created nor modified. + * Returns the depth used for sync. By default, this is null and repo + * will sync the entire history. */ + @Exported + public int getDepth() { + return depth; + } + /** + * Returns the contents of the local_manifests/local.xml. By default, this is null + * and a local_manifests/local.xml is neither created nor modified. + */ + @Exported public String getLocalManifest() { return localManifest; } @@ -126,203 +277,915 @@ public String getLocalManifest() { * Returns the destination directory. By default, this is null and the * source is synced to the root of the workspace. */ + @Exported public String getDestinationDir() { return destinationDir; } + /** + * returns list of ignore projects. + */ + @Exported + public String getIgnoreProjects() { + return StringUtils.join(ignoreProjects, '\n'); + } + + /** + * Returns the value of currentBranch. + */ + @Exported + public boolean isCurrentBranch() { + return currentBranch; + } + /** + * Returns the value of resetFirst. + */ + @Exported + public boolean isResetFirst() { + return resetFirst; + } + + /** + * Returns the value of cleanFirst. + */ + @Exported + public boolean isCleanFirst() { + return cleanFirst; + } + + /** + * Returns the value of showAllChanges. + */ + @Exported + public boolean isShowAllChanges() { + return showAllChanges; + } + + /** + * Returns the value of quiet. + */ + @Exported + public boolean isQuiet() { + return quiet; + } + /** + * Returns the value of forceSync. + */ + @Exported + public boolean isForceSync() { + return forceSync; + } + + /** + * Returns the value of trace. + */ + @Exported + public boolean isTrace() { + return trace; + } + + /** + * Returns the value of noTags. + */ + @Exported + public boolean isNoTags() { + return noTags; + } + /** + * Returns the value of noCloneBundle. + */ + @Exported + public boolean isNoCloneBundle() { + return noCloneBundle; + } + /** + * Returns the value of isWorktree. + */ + @Exported + public boolean isWorktree() { + return worktree; + } + + /** + * Returns the value of manifestSubmodules. + */ + @Exported + public boolean isManifestSubmodules() { + return manifestSubmodules; + } + + /** + * Returns the value of fetchSubmodules. + */ + public boolean isFetchSubmodules() { + return fetchSubmodules; + } + + /** + * Returns the value of gitLfs. + */ + public boolean isGitLfs() { + return gitLfs; + } + + /** + * Returns the value of extraEnvVars. + */ + @Exported + public Map getExtraEnvVars() { + return extraEnvVars; + } + + /** + * Returns the value of noSync. + */ + public boolean isNoSync() { + return noSync; + } + /** * The constructor takes in user parameters and sets them. Each job using * the RepoSCM will call this constructor. * - * @param manifestRepositoryUrl - * The URL for the manifest repository. - * @param manifestBranch - * The branch of the manifest repository. Typically this is null - * or the empty string, which will cause repo to default to - * "master". - * @param manifestFile - * The file to use as the repository manifest. Typically this is - * null which will cause repo to use the default of "default.xml" - * @param mirrorDir - * The path of the mirror directory to reference when - * initializing repo. - * @param jobs - * The number of concurrent jobs to use for the sync command. If - * this is 0 or negative the jobs parameter is not specified. - * @param localManifest - * If not null this string is written to .repo/local_manifest.xml - * @param destinationDir - * If not null then the source is synced to the destinationDir - * subdirectory of the workspace. + * @param manifestRepositoryUrl The URL for the manifest repository. + * @param manifestBranch The branch of the manifest repository. Typically this is null + * or the empty string, which will cause repo to default to + * "master". + * @param manifestFile The file to use as the repository manifest. Typically this is + * null which will cause repo to use the default of "default.xml" + * @param manifestGroup The group name for the projects that need to be fetched. + * Typically, this is null and all projects tagged 'default' will + * be fetched. + * @param mirrorDir The path of the mirror directory to reference when + * initializing repo. + * @param jobs The number of concurrent jobs to use for the sync command. If + * this is 0 or negative the jobs parameter is not specified. + * @param depth This is the depth to use when syncing. By default this is 0 + * and the full history is synced. + * @param localManifest May be null, a string containing XML, or an URL. + * If XML, this string is written to + * .repo/local_manifests/local.xml + * If an URL, the URL is fetched and the content is written + * to .repo/local_manifests/local.xml + * @param destinationDir If not null then the source is synced to the destinationDir + * subdirectory of the workspace. + * @param repoUrl If not null then use this url as repo base, + * instead of the default. + * @param currentBranch If this value is true, add the "-c" option when executing + * "repo sync". + * @param resetFirst If this value is true, do "repo forall -c 'git reset --hard'" + * before syncing. + * @param quiet If this value is true, add the "-q" option when executing + * "repo sync". + * @param trace If this value is true, add the "--trace" option when + * executing "repo init" and "repo sync". + * @param showAllChanges If this value is true, add the "--first-parent" option to + * "git log" when determining changesets. + * */ - @DataBoundConstructor + @Deprecated public RepoScm(final String manifestRepositoryUrl, - final String manifestBranch, final String manifestFile, - final String mirrorDir, final int jobs, - final String localManifest, final String destinationDir) { + final String manifestBranch, final String manifestFile, + final String manifestGroup, final String mirrorDir, final int jobs, + final int depth, + final String localManifest, final String destinationDir, + final String repoUrl, + final boolean currentBranch, + final boolean resetFirst, + final boolean quiet, + final boolean trace, + final boolean showAllChanges) { + this(manifestRepositoryUrl); + setManifestBranch(manifestBranch); + setManifestGroup(manifestGroup); + setManifestFile(manifestFile); + setMirrorDir(mirrorDir); + setJobs(jobs); + setDepth(depth); + setLocalManifest(localManifest); + setDestinationDir(destinationDir); + setCurrentBranch(currentBranch); + setResetFirst(resetFirst); + setCleanFirst(false); + setQuiet(quiet); + setTrace(trace); + setShowAllChanges(showAllChanges); + setRepoUrl(repoUrl); + ignoreProjects = Collections.emptySet(); + setWorktree(false); + } + + /** + * The constructor takes in user parameters and sets them. Each job using + * the RepoSCM will call this constructor. + * + * @param manifestRepositoryUrl The URL for the manifest repository. + */ + @DataBoundConstructor + public RepoScm(final String manifestRepositoryUrl) { this.manifestRepositoryUrl = manifestRepositoryUrl; + manifestFile = null; + manifestGroup = null; + repoUrl = null; + repoBranch = null; + mirrorDir = null; + manifestBranch = null; + jobs = 0; + depth = 0; + localManifest = null; + destinationDir = null; + currentBranch = false; + resetFirst = false; + cleanFirst = false; + quiet = false; + forceSync = false; + trace = false; + showAllChanges = false; + noTags = false; + manifestSubmodules = false; + fetchSubmodules = false; + ignoreProjects = Collections.emptySet(); + noCloneBundle = false; + worktree = false; + noSync = false; + gitLfs = false; + } + + /** + * Set the manifest branch name. + * + * @param manifestBranch + * The branch of the manifest repository. Typically this is null + * or the empty string, which will cause repo to default to + * "master". + */ + @DataBoundSetter + public void setManifestBranch(@CheckForNull final String manifestBranch) { this.manifestBranch = Util.fixEmptyAndTrim(manifestBranch); + } + + /** + * Set the initial manifest file name. + * + * @param manifestFile + * The file to use as the repository manifest. Typically this is + * null which will cause repo to use the default of "default.xml" + */ + @DataBoundSetter + public void setManifestFile(@CheckForNull final String manifestFile) { this.manifestFile = Util.fixEmptyAndTrim(manifestFile); + } + + /** + * Set the group of projects to fetch. + * + * @param manifestGroup + * The group name for the projects that need to be fetched. + * Typically, this is null and all projects tagged 'default' will + * be fetched. + */ + @DataBoundSetter + public void setManifestGroup(@CheckForNull final String manifestGroup) { + this.manifestGroup = Util.fixEmptyAndTrim(manifestGroup); + } + + /** + * Set the platform of projects to fetch. + * + * @param manifestPlatform + * The platform for the projects that need to be fetched. + * Typically, this is null and only projects for the current platform + * will be fetched. + */ + @DataBoundSetter + public void setManifestPlatform(@CheckForNull final String manifestPlatform) { + this.manifestPlatform = Util.fixEmptyAndTrim(manifestPlatform); + } + + /** + * Set the name of the mirror directory. + * + * @param mirrorDir + * The path of the mirror directory to reference when + * initializing repo. + */ + @DataBoundSetter + public void setMirrorDir(@CheckForNull final String mirrorDir) { this.mirrorDir = Util.fixEmptyAndTrim(mirrorDir); + } + + /** + * Set the number of jobs used for sync. + * + * @param jobs + * The number of concurrent jobs to use for the sync command. If + * this is 0 or negative the jobs parameter is not specified. + */ + @DataBoundSetter + public void setJobs(final int jobs) { this.jobs = jobs; + } + + /** + * Set the depth used for sync. + * + * @param depth + * This is the depth to use when syncing. By default this is 0 + * and the full history is synced. + */ + @DataBoundSetter + public void setDepth(final int depth) { + this.depth = depth; + } + + /** + * Set the content of the local manifest. + * + * @param localManifest + * May be null, a string containing XML, or an URL. + * If XML, this string is written to .repo/local_manifests/local.xml + * If an URL, the URL is fetched and the content is written + * to .repo/local_manifests/local.xml + */ + @DataBoundSetter + public void setLocalManifest(@CheckForNull final String localManifest) { this.localManifest = Util.fixEmptyAndTrim(localManifest); + } + + /** + * Set the destination directory. + * + * @param destinationDir + * If not null then the source is synced to the destinationDir + * subdirectory of the workspace. + */ + @DataBoundSetter + public void setDestinationDir(@CheckForNull final String destinationDir) { this.destinationDir = Util.fixEmptyAndTrim(destinationDir); - // TODO: repoUrl - this.repoUrl = null; + } + + /** + * Set currentBranch. + * + * @param currentBranch + * If this value is true, add the "-c" option when executing + * "repo sync". + */ + @DataBoundSetter + public void setCurrentBranch(final boolean currentBranch) { + this.currentBranch = currentBranch; + } + + /** + * Set resetFirst. + * + * @param resetFirst + * If this value is true, do "repo forall -c 'git reset --hard'" + * before syncing. + */ + @DataBoundSetter + public void setResetFirst(final boolean resetFirst) { + this.resetFirst = resetFirst; + } + + /** + * Set cleanFirst. + * + * @param cleanFirst + * If this value is true, do "repo forall -c 'git clean -fdx'" + * before syncing. + */ + @DataBoundSetter + public void setCleanFirst(final boolean cleanFirst) { + this.cleanFirst = cleanFirst; + } + + /** + * Set quiet. + * + * @param quiet + * * If this value is true, add the "-q" option when executing + * "repo sync". + */ + @DataBoundSetter + public void setQuiet(final boolean quiet) { + this.quiet = quiet; + } + + /** + * Set trace. + * + * @param trace + * If this value is true, add the "--trace" option when + * executing "repo init" and "repo sync". + */ + + @DataBoundSetter + public void setTrace(final boolean trace) { + this.trace = trace; + } + + /** + * Set showAllChanges. + * + * @param showAllChanges + * If this value is true, add the "--first-parent" option to + * "git log" when determining changesets. + */ + @DataBoundSetter + public void setShowAllChanges(final boolean showAllChanges) { + this.showAllChanges = showAllChanges; + } + + /** + * Set noCloneBundle. + * + * @param noCloneBundle + * If this value is true, add the "--no-clone-bundle" option when + * running the "repo init" and "repo sync" commands. + */ + @DataBoundSetter + public void setNoCloneBundle(final boolean noCloneBundle) { + this.noCloneBundle = noCloneBundle; + } + + /** + * Set worktree. + * + * @param worktree + * If this value is true, add the "--worktree" option when + * running the "repo init" command. + */ + @DataBoundSetter + public void setWorktree(final boolean worktree) { + this.worktree = worktree; + } + + /** + * Set the repo url. + * + * @param repoUrl + * If not null then use this url as repo base, + * instead of the default + */ + @DataBoundSetter + public void setRepoUrl(@CheckForNull final String repoUrl) { + this.repoUrl = Util.fixEmptyAndTrim(repoUrl); + } + + /** + * Set the repo branch. + * + * @param repoBranch + * If not null then use this as branch for repo itself + * instead of the default. + */ + @DataBoundSetter + public void setRepoBranch(@CheckForNull final String repoBranch) { + this.repoBranch = Util.fixEmptyAndTrim(repoBranch); + } + + /** + * Enables --force-sync option on repo sync command. + * @param forceSync + * If this value is true, add the "--force-sync" option when + * executing "repo sync". + */ + @DataBoundSetter + public void setForceSync(final boolean forceSync) { + this.forceSync = forceSync; + } + + /** + * disables -sync option on repo command. + * @param noSync + * If this value is true, do not add the "-sync" option when + * executing "repo ". + */ + @DataBoundSetter + public void setNoSync(final boolean noSync) { + this.noSync = noSync; + } + + /** + * Set noTags. + * + * @param noTags + * If this value is true, add the "--no-tags" option when + * executing "repo sync". + */ + @DataBoundSetter + public final void setNoTags(final boolean noTags) { + this.noTags = noTags; + } + + /** + * Set manifestSubmodules. + * + * @param manifestSubmodules + * If this value is true, add the "--submodules" option when + * executing "repo init". + */ + @DataBoundSetter + public void setManifestSubmodules(final boolean manifestSubmodules) { + this.manifestSubmodules = manifestSubmodules; + } + + /** + * Set fetchSubmodules. + * + * @param fetchSubmodules + * If this value is true, add the "--fetch-submodules" option when + * executing "repo sync". + */ + @DataBoundSetter + public void setFetchSubmodules(final boolean fetchSubmodules) { + this.fetchSubmodules = fetchSubmodules; + } + + /** + * Set gitLfs. + * + * @param gitLfs + * If this value is true, add the "--git-lfs" option when + * executing "repo init". + */ + @DataBoundSetter + public void setGitLfs(final boolean gitLfs) { + this.gitLfs = gitLfs; + } + + /** + * Sets list of projects which changes will be ignored when + * calculating whether job needs to be rebuild. This field corresponds + * to serverpath i.e. "name" section of the manifest. + * @param ignoreProjects + * String representing project names separated by " ". + */ + @DataBoundSetter + public final void setIgnoreProjects(final String ignoreProjects) { + if (ignoreProjects == null) { + this.ignoreProjects = Collections.emptySet(); + return; + } + this.ignoreProjects = new LinkedHashSet( + Arrays.asList(ignoreProjects.split("\\s+"))); + } + + /** + * Set additional environment variables to use. These variables will override + * any parameter from the project or variable set in environment already. + * @param extraEnvVars + * Additional environment variables to set. + */ + @DataBoundSetter + public void setExtraEnvVars(@CheckForNull final Map extraEnvVars) { + this.extraEnvVars = extraEnvVars != null ? new EnvVars(extraEnvVars) : null; } @Override public SCMRevisionState calcRevisionsFromBuild( - final AbstractBuild build, final Launcher launcher, - final TaskListener listener) throws IOException, - InterruptedException { + @Nonnull final Run build, @Nullable final FilePath workspace, + @Nullable final Launcher launcher, @Nonnull final TaskListener listener + ) throws IOException, InterruptedException { // We add our SCMRevisionState from within checkout, so this shouldn't // be called often. However it will be called if this is the first // build, if a build was aborted before it reported the repository // state, etc. - return null; + return SCMRevisionState.NONE; + } + + private boolean shouldIgnoreChanges(final RevisionState current, final RevisionState baseline) { + List changedProjects = current.whatChanged(baseline); + if ((changedProjects == null) || (ignoreProjects == null)) { + return false; + } + if (ignoreProjects.isEmpty()) { + return false; + } + + + // Check for every changed item if it is not contained in the + // ignored setting .. project must be rebuilt + for (ProjectState changed : changedProjects) { + if (!ignoreProjects.contains(changed.getServerPath())) { + return false; + } + } + return true; } @Override - protected PollingResult compareRemoteRevisionWith( - final AbstractProject project, final Launcher launcher, - final FilePath workspace, final TaskListener listener, - final SCMRevisionState baseline) throws IOException, + public PollingResult compareRemoteRevisionWith( + @Nonnull final Job job, @Nullable final Launcher launcher, + @Nullable final FilePath workspace, @Nonnull final TaskListener listener, + @Nonnull final SCMRevisionState baseline) throws IOException, InterruptedException { SCMRevisionState myBaseline = baseline; - if (myBaseline == null) { + final EnvVars env = getEnvVars(null, job); + final String expandedManifestUrl = env.expand(manifestRepositoryUrl); + final String expandedManifestBranch = env.expand(manifestBranch); + final String expandedManifestFile = env.expand(manifestFile); + final Run lastRun = job.getLastBuild(); + + if (myBaseline == SCMRevisionState.NONE) { // Probably the first build, or possibly an aborted build. - myBaseline = getLastState(project.getLastBuild()); - if (myBaseline == null) { + myBaseline = getLastState(lastRun, expandedManifestUrl, + expandedManifestBranch, expandedManifestFile); + if (myBaseline == SCMRevisionState.NONE) { return PollingResult.BUILD_NOW; } } FilePath repoDir; if (destinationDir != null) { - repoDir = workspace.child(destinationDir); - if (!repoDir.isDirectory()) { - repoDir.mkdirs(); - } + repoDir = workspace.child(env.expand(destinationDir)); } else { repoDir = workspace; } - if (!checkoutCode(launcher, repoDir, listener.getLogger())) { + if (!repoDir.isDirectory()) { + repoDir.mkdirs(); + } + + if (!checkoutCode(launcher, repoDir, env, listener.getLogger())) { // Some error occurred, try a build now so it gets logged. return new PollingResult(myBaseline, myBaseline, Change.INCOMPARABLE); } - final RevisionState currentState = - new RevisionState(getStaticManifest(launcher, repoDir, - listener.getLogger()), manifestBranch, - listener.getLogger()); + final RevisionState currentState = new RevisionState( + getStaticManifest(launcher, repoDir, listener.getLogger(), env), + getManifestRevision(launcher, repoDir, listener.getLogger(), env), + expandedManifestUrl, expandedManifestBranch, expandedManifestFile, + listener.getLogger()); + final Change change; if (currentState.equals(myBaseline)) { change = Change.NONE; } else { - change = Change.SIGNIFICANT; + if (shouldIgnoreChanges(currentState, + myBaseline instanceof RevisionState ? (RevisionState) myBaseline : null)) { + change = Change.NONE; + } else { + change = Change.SIGNIFICANT; + } } return new PollingResult(myBaseline, currentState, change); } @Override - public boolean checkout( - @SuppressWarnings("rawtypes") final AbstractBuild build, - final Launcher launcher, final FilePath workspace, - final BuildListener listener, final File changelogFile) + public void checkout( + @Nonnull final Run build, @Nonnull final Launcher launcher, + @Nonnull final FilePath workspace, @Nonnull final TaskListener listener, + @CheckForNull final File changelogFile, @CheckForNull final SCMRevisionState baseline) throws IOException, InterruptedException { + if (!ALLOW_LOCAL_CHECKOUT && !workspace.isRemote()) { + abortIfUrlLocal(); + } + + Job job = build.getParent(); + EnvVars env = build.getEnvironment(listener); + env = getEnvVars(env, job); + FilePath repoDir; if (destinationDir != null) { - repoDir = workspace.child(destinationDir); - if (!repoDir.isDirectory()) { - repoDir.mkdirs(); - } + repoDir = workspace.child(env.expand(destinationDir)); } else { repoDir = workspace; } - if (!checkoutCode(launcher, repoDir, listener.getLogger())) { - return false; + if (!repoDir.isDirectory()) { + repoDir.mkdirs(); + } + + if (!checkoutCode(launcher, repoDir, env, listener.getLogger())) { + throw new IOException("Could not checkout"); } final String manifest = - getStaticManifest(launcher, repoDir, listener.getLogger()); + getStaticManifest(launcher, repoDir, listener.getLogger(), env); + final String manifestRevision = + getManifestRevision(launcher, repoDir, listener.getLogger(), env); + final String expandedUrl = env.expand(manifestRepositoryUrl); + final String expandedBranch = env.expand(manifestBranch); + final String expandedFile = env.expand(manifestFile); final RevisionState currentState = - new RevisionState(manifest, manifestBranch, - listener.getLogger()); + new RevisionState(manifest, manifestRevision, expandedUrl, + expandedBranch, expandedFile, listener.getLogger()); build.addAction(currentState); - final RevisionState previousState = - getLastState(build.getPreviousBuild()); - ChangeLog.saveChangeLog(currentState, previousState, changelogFile, - launcher, repoDir); - build.addAction(new TagAction(build)); - return true; + final Run previousBuild = build.getPreviousBuild(); + final SCMRevisionState previousState = + getLastState(previousBuild, expandedUrl, expandedBranch, expandedFile); + + if (changelogFile != null) { + ChangeLog.saveChangeLog( + currentState, + previousState == SCMRevisionState.NONE ? null : (RevisionState) previousState, + changelogFile, + launcher, + repoDir, + showAllChanges); + } + + if (build.getActions(ManifestAction.class).size() == 0) { + build.addAction(new ManifestAction(build)); + } } - private int doSync(final Launcher launcher, final FilePath workspace, - final OutputStream logger) + private void abortIfUrlLocal() throws AbortException { + if (StringUtils.isNotEmpty(manifestRepositoryUrl) + && (manifestRepositoryUrl.toLowerCase(Locale.ENGLISH).startsWith("file://") + || Files.exists(Paths.get(manifestRepositoryUrl)))) { + throw new AbortException("Checkout of Repo url '" + manifestRepositoryUrl + + "' aborted because it references a local directory, " + + "which may be insecure. " + + "You can allow local checkouts anyway by setting the system property '" + + ALLOW_LOCAL_CHECKOUT_PROPERTY + "' to true."); + } + } + + private int doSync(final Launcher launcher, @Nonnull final FilePath workspace, + final OutputStream logger, final EnvVars env) throws IOException, InterruptedException { final List commands = new ArrayList(4); debug.log(Level.FINE, "Syncing out code in: " + workspace.getName()); commands.clear(); + if (resetFirst) { + commands.add(getDescriptor().getExecutable()); + commands.add("forall"); + if (jobs > 0) { + commands.add("--jobs=" + jobs); + } + commands.add("-c"); + commands.add("git reset --hard"); + int resetCode = launcher.launch().stdout(logger) + .stderr(logger).pwd(workspace).cmds(commands).envs(env).join(); + + if (resetCode != 0) { + debug.log(Level.WARNING, "Failed to reset first."); + } + commands.clear(); + } + if (cleanFirst) { + commands.add(getDescriptor().getExecutable()); + commands.add("forall"); + if (jobs > 0) { + commands.add("--jobs=" + jobs); + } + commands.add("-c"); + commands.add("git clean -fdx"); + int cleanCode = launcher.launch().stdout(logger) + .stderr(logger).pwd(workspace).cmds(commands).envs(env).join(); + + if (cleanCode != 0) { + debug.log(Level.WARNING, "Failed to clean first."); + } + commands.clear(); + } commands.add(getDescriptor().getExecutable()); + if (trace) { + commands.add("--trace"); + } commands.add("sync"); commands.add("-d"); + if (isCurrentBranch()) { + commands.add("-c"); + } + if (isQuiet()) { + commands.add("-q"); + } + if (isForceSync()) { + commands.add("--force-sync"); + } if (jobs > 0) { commands.add("--jobs=" + jobs); } - int returnCode = - launcher.launch().stdout(logger).pwd(workspace) - .cmds(commands).join(); - return returnCode; + if (isNoTags()) { + commands.add("--no-tags"); + } + if (isNoCloneBundle()) { + commands.add("--no-clone-bundle"); + } + if (fetchSubmodules) { + commands.add("--fetch-submodules"); + } + if (repoUrl != null) { + // When repoUrl is set, we allow for unsigned repo tool + // versions; so --no-repo-verify must be set both in init + // and sync. + commands.add("--no-repo-verify"); + } + return launcher.launch().stdout(logger).pwd(workspace) + .cmds(commands).envs(env).join(); } private boolean checkoutCode(final Launcher launcher, - final FilePath workspace, final OutputStream logger) + @Nonnull final FilePath workspace, + final EnvVars env, + final OutputStream logger) throws IOException, InterruptedException { final List commands = new ArrayList(4); - debug.log(Level.INFO, "Checking out code in: " + workspace.getName()); + debug.log(Level.INFO, "Checking out code in: {0}", workspace.getName()); + + FilePath rdir = workspace.child(".repo"); + FilePath lmdir = rdir.child("local_manifests"); + if (rdir.exists()) { + // Delete the legacy local_manifest.xml in case it exists from a previous build + rdir.child("local_manifest.xml").delete(); + + if (lmdir.exists()) { + // Delete contents of local_manifests in case it exists from a previous build + lmdir.deleteContents(); + } + } commands.add(getDescriptor().getExecutable()); + if (trace) { + commands.add("--trace"); + } commands.add("init"); commands.add("-u"); - commands.add(manifestRepositoryUrl); + commands.add(env.expand(manifestRepositoryUrl)); if (manifestBranch != null) { commands.add("-b"); - commands.add(manifestBranch); + commands.add(env.expand(manifestBranch)); } if (manifestFile != null) { commands.add("-m"); - commands.add(manifestFile); + commands.add(env.expand(manifestFile)); } if (mirrorDir != null) { - commands.add("--reference=" + mirrorDir); + commands.add("--reference=" + env.expand(mirrorDir)); } if (repoUrl != null) { - commands.add("--repo-url=" + repoUrl); + commands.add("--repo-url=" + env.expand(repoUrl)); commands.add("--no-repo-verify"); } + if (repoBranch != null) { + commands.add("--repo-branch=" + env.expand(repoBranch)); + } + if (manifestGroup != null) { + commands.add("-g"); + commands.add(env.expand(manifestGroup)); + } + if (manifestPlatform != null) { + commands.add("-p"); + commands.add(env.expand(manifestPlatform)); + } + if (depth != 0) { + commands.add("--depth=" + depth); + } + if (isNoCloneBundle()) { + commands.add("--no-clone-bundle"); + } + if (isWorktree()) { + commands.add("--worktree"); + } + if (currentBranch) { + commands.add("--current-branch"); + } + if (noTags) { + commands.add("--no-tags"); + } + if (manifestSubmodules) { + commands.add("--submodules"); + } + if (gitLfs) { + commands.add("--git-lfs"); + } int returnCode = launcher.launch().stdout(logger).pwd(workspace) - .cmds(commands).join(); + .cmds(commands).envs(env).join(); if (returnCode != 0) { return false; } - if (workspace != null) { - FilePath rdir = workspace.child(".repo"); - FilePath lm = rdir.child("local_manifest.xml"); - if (localManifest != null) { - lm.write(localManifest, null); + if (isNoSync()) { + debug.log(Level.FINEST, "Repo init completed successfully, not running repo sync"); + return true; + } + + if (localManifest != null) { + if (!lmdir.exists()) { + lmdir.mkdirs(); + } + FilePath lm = lmdir.child("local.xml"); + String expandedLocalManifest = env.expand(localManifest); + if (expandedLocalManifest.startsWith(" commands = new ArrayList<>(); + commands.add(getDescriptor().getExecutable()); + commands.add("manifest"); + launcher.launch().stderr(logger).stdout(byteArrayOutputStream).pwd(workspace) + .cmds(commands).envs(env).join(); + return byteArrayOutputStream.toByteArray(); + } + + private void abortIfManifestReferencesLocalUrl(final Launcher launcher, + final FilePath workspace, + final OutputStream logger, + final EnvVars env) + throws IOException, InterruptedException { + byte[] manifestText = getManifestAsBytes(launcher, workspace, logger, env); + + ManifestValidator.validate(manifestText, manifestRepositoryUrl); + } + + /** + * Adds environmental variables for the builds to the given map. + */ + @Override + public void buildEnvironment( + @Nonnull final Run build, + @Nonnull final java.util.Map env) { + final Job job = build.getParent(); + final EnvVars jobEnv = getEnvVars(null, job); + + final String expandedManifestUrl = jobEnv.expand(manifestRepositoryUrl); + final String expandedManifestBranch = jobEnv.expand(manifestBranch); + final String expandedManifestFile = jobEnv.expand(manifestFile); + + SCMRevisionState state = getState(build, expandedManifestUrl, + expandedManifestBranch, expandedManifestFile); + + if (state != SCMRevisionState.NONE) { + env.put("REPO_MANIFEST_URL", ((RevisionState) state).getUrl()); + env.put("REPO_MANIFEST_BRANCH", ((RevisionState) state).getBranch()); + env.put("REPO_MANIFEST_FILE", ((RevisionState) state).getFile()); + String manifest = ((RevisionState) state).getManifest(); + if (manifest.length() > MAX_ARG_STRLEN) { + manifest = "_"; + } + env.put("REPO_MANIFEST_XML", manifest); + } + } + private String getStaticManifest(final Launcher launcher, - final FilePath workspace, final OutputStream logger) + final FilePath workspace, final OutputStream logger, + final EnvVars env) throws IOException, InterruptedException { final ByteArrayOutputStream output = new ByteArrayOutputStream(); final List commands = new ArrayList(6); @@ -352,22 +1269,78 @@ private String getStaticManifest(final Launcher launcher, commands.add("-r"); // TODO: should we pay attention to the output from this? launcher.launch().stderr(logger).stdout(output).pwd(workspace) - .cmds(commands).join(); - final String manifestText = output.toString(); + .cmds(commands).envs(env).join(); + final String manifestText = new String(output.toByteArray(), Charset.defaultCharset()); debug.log(Level.FINEST, manifestText); return manifestText; } - private RevisionState getLastState(final Run lastBuild) { + private String getManifestRevision(final Launcher launcher, + final FilePath workspace, final OutputStream logger, + final EnvVars env) + throws IOException, InterruptedException { + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + final List commands = new ArrayList(6); + commands.add("git"); + commands.add("rev-parse"); + commands.add("HEAD"); + launcher.launch().stderr(logger).stdout(output).pwd( + new FilePath(workspace, ".repo/manifests")) + .cmds(commands).envs(env).join(); + final String manifestText = new String(output.toByteArray(), + Charset.defaultCharset()).trim(); + debug.log(Level.FINEST, manifestText); + return manifestText; + } + + private boolean isRelevantState(final RevisionState state, final String url, + final String branch, final String file) { + return StringUtils.equals(state.getBranch(), branch) + && StringUtils.equals(state.getUrl(), url) + && StringUtils.equals(state.getFile(), file); + } + + @Nonnull + private SCMRevisionState getState(final Run build, + final String expandedManifestUrl, + final String expandedManifestBranch, + final String expandedManifestFile) { + if (build == null) { + return SCMRevisionState.NONE; + } + + final List stateList = + build.getActions(RevisionState.class); + for (RevisionState state : stateList) { + if (state != null + && isRelevantState(state, expandedManifestUrl, + expandedManifestBranch, expandedManifestFile)) { + return state; + } + } + + return SCMRevisionState.NONE; + } + + @Nonnull + private SCMRevisionState getLastState(final Run lastBuild, + final String expandedManifestUrl, + final String expandedManifestBranch, + final String expandedManifestFile) { if (lastBuild == null) { - return null; + return SCMRevisionState.NONE; } - final RevisionState lastState = - lastBuild.getAction(RevisionState.class); - if (lastState != null && lastState.getBranch() == manifestBranch) { - return lastState; + + SCMRevisionState lastState = getState(lastBuild, expandedManifestUrl, + expandedManifestBranch, expandedManifestFile); + + if (lastState == SCMRevisionState.NONE) { + lastState = getLastState(lastBuild.getPreviousBuild(), + expandedManifestUrl, expandedManifestBranch, + expandedManifestFile); } - return getLastState(lastBuild.getPreviousBuild()); + + return lastState; } @Override @@ -380,8 +1353,21 @@ public DescriptorImpl getDescriptor() { return (DescriptorImpl) super.getDescriptor(); } + @Nonnull + @Override + public String getKey() { + return new StringBuilder("repo") + .append(' ') + .append(getManifestRepositoryUrl()) + .append(' ') + .append(getManifestFile()) + .append(' ') + .append(getManifestBranch()) + .toString(); + } + /** - * A DescriptorImpl contains variables used server-wide. In our case, we + * A DescriptorImpl contains variables used server-wide. In our263 case, we * only store the path to the repo executable, which defaults to just * "repo". This class also handles some Jenkins housekeeping. */ @@ -404,7 +1390,7 @@ public String getDisplayName() { } @Override - public boolean configure(final StaplerRequest req, + public boolean configure(final StaplerRequest2 req, final JSONObject json) throws hudson.model.Descriptor.FormException { repoExecutable = @@ -437,5 +1423,10 @@ public String getExecutable() { return repoExecutable; } } + + @Override + public boolean isApplicable(final Job project) { + return true; + } } } diff --git a/src/main/java/hudson/plugins/repo/RevisionState.java b/src/main/java/hudson/plugins/repo/RevisionState.java index 6ed583f..0826a15 100644 --- a/src/main/java/hudson/plugins/repo/RevisionState.java +++ b/src/main/java/hudson/plugins/repo/RevisionState.java @@ -33,29 +33,30 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; -import javax.xml.parsers.DocumentBuilderFactory; +import javax.annotation.Nullable; +import jenkins.util.xml.XMLUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; /** * A RevisionState records the state of the repository for a particular build. * It is used to see what changed from build to build. */ @SuppressWarnings("serial") -public class RevisionState extends SCMRevisionState implements Serializable { +class RevisionState extends SCMRevisionState implements Serializable { private final String manifest; private final Map projects = new TreeMap(); + private final String url; private final String branch; + private final String file; private static Logger debug = Logger.getLogger("hudson.plugins.repo.RevisionState"); @@ -65,24 +66,30 @@ public class RevisionState extends SCMRevisionState implements Serializable { * * @param manifest * A string representation of the static manifest XML file + * @param manifestRevision + * Git hash of the manifest repo + * @param url + * The URL of the manifest * @param branch * The branch of the manifest project + * @param file + * The path to the manifest file * @param logger * A PrintStream for logging errors */ - public RevisionState(final String manifest, final String branch, - final PrintStream logger) { + RevisionState(final String manifest, final String manifestRevision, + final String url, final String branch, final String file, + @Nullable final PrintStream logger) { this.manifest = manifest; + this.url = url; this.branch = branch; + this.file = file; try { - final InputSource xmlSource = new InputSource(); - xmlSource.setCharacterStream(new StringReader(manifest)); - final Document doc = - DocumentBuilderFactory.newInstance().newDocumentBuilder() - .parse(xmlSource); - + final Document doc = XMLUtils.parse(new StringReader(manifest)); if (!doc.getDocumentElement().getNodeName().equals("manifest")) { - logger.println("Error - malformed manifest"); + if (logger != null) { + logger.println("Error - malformed manifest"); + } return; } final NodeList projectNodes = doc.getElementsByTagName("project"); @@ -104,17 +111,27 @@ public RevisionState(final String manifest, final String branch, path = serverPath; } if (path != null && serverPath != null && revision != null) { - projects.put(path, new ProjectState(path, serverPath, - revision)); + projects.put(path, ProjectState.constructCachedInstance( + path, serverPath, revision)); if (logger != null) { logger.println("Added a project: " + path + " at revision: " + revision); } } } + + final String manifestP = ".repo/manifests.git"; + projects.put(manifestP, ProjectState.constructCachedInstance( + manifestP, manifestP, manifestRevision)); + if (logger != null) { + logger.println("Manifest at revision: " + manifestRevision); + } + + } catch (final Exception e) { - logger.println(e); - return; + if (logger != null) { + logger.println(e); + } } } @@ -136,7 +153,19 @@ public boolean equals(final Object obj) { @Override public int hashCode() { - return branch.hashCode() ^ manifest.hashCode() ^ projects.hashCode(); + return (branch != null ? branch.hashCode() : 0) + ^ (url != null ? url.hashCode() : 0) + ^ (file != null ? file.hashCode() : 0) + ^ (manifest != null ? manifest.hashCode() : 0) + ^ projects.hashCode(); + } + + /** + * Returns the manifest repository's url when this state was + * created. + */ + public String getUrl() { + return url; } /** @@ -147,6 +176,13 @@ public String getBranch() { return branch; } + /** + * Returns the path to the manifest file used when this state was created. + */ + public String getFile() { + return file; + } + /** * Returns the static XML manifest for this repository state in String form. */ @@ -174,7 +210,7 @@ public String getRevision(final String path) { * @return A List of ProjectStates from the previous repo state which have * since been updated. */ - public List whatChanged(final RevisionState previousState) { + List whatChanged(@Nullable final RevisionState previousState) { final List changes = new ArrayList(); if (previousState == null) { // Everything is new. The change log would include every change, @@ -183,21 +219,22 @@ public List whatChanged(final RevisionState previousState) { debug.log(Level.FINE, "Everything is new"); return null; } - final Set keys = projects.keySet(); + //final Set keys = projects.keySet(); HashMap previousStateCopy = new HashMap(previousState.projects); - for (final String key : keys) { - final ProjectState status = previousStateCopy.get(key); + for (final Map.Entry entry : projects.entrySet()) { + final ProjectState status = previousStateCopy.get(entry.getKey()); if (status == null) { // This is a new project, just added to the manifest. - final ProjectState newProject = projects.get(key); - debug.log(Level.FINE, "New project: " + key); - changes.add(new ProjectState(newProject.getPath(), newProject - .getServerPath(), null)); - } else if (!status.equals(projects.get(key))) { - changes.add(previousStateCopy.get(key)); + final ProjectState newProject = entry.getValue(); + debug.log(Level.FINE, "New project: {0}", entry.getKey()); + changes.add(ProjectState.constructCachedInstance( + newProject.getPath(), newProject.getServerPath(), + null)); + } else if (!status.equals(entry.getValue())) { + changes.add(previousStateCopy.get(entry.getKey())); } - previousStateCopy.remove(key); + previousStateCopy.remove(entry.getKey()); } changes.addAll(previousStateCopy.values()); return changes; diff --git a/src/main/java/hudson/plugins/repo/StaticManifest.java b/src/main/java/hudson/plugins/repo/StaticManifest.java new file mode 100644 index 0000000..8e54b3b --- /dev/null +++ b/src/main/java/hudson/plugins/repo/StaticManifest.java @@ -0,0 +1,82 @@ +/* + * The MIT License + * + * Copyright (c) 2010, Brad Larson + * + * 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 hudson.plugins.repo; + +/** + * A POJO containing information about a static manifest. + */ +public class StaticManifest { + private String file; + private String branch; + private String url; + private String manifest; + + /** + * Create a new ModifiedFile object with the given path and edit type. + * + * @param file + * the path to the manifest file for this static manifest + * @param branch + * the manifest repository's branch name for this static manifes + * @param url + * he manifest repository's url for this static manifest + * @param manifest + * the content of this static manifest + */ + public StaticManifest(final String file, final String branch, + final String url, final String manifest) { + this.file = file; + this.branch = branch; + this.url = url; + this.manifest = manifest; + } + + /** + * Returns the path to the manifest file for this static manifest. + */ + public String getFile() { + return file; + } + + /** + * Returns the manifest repository's branch name for this static manifest. + */ + public String getBranch() { + return branch; + } + + /** + * Returns the manifest repository's url for this static manifest. + */ + public String getUrl() { + return url; + } + + /** + * Returns the content of this static manifest. + */ + public String getManifest() { + return manifest; + } +} diff --git a/src/main/java/hudson/plugins/repo/TagAction.java b/src/main/java/hudson/plugins/repo/TagAction.java index 36575ef..38d692c 100644 --- a/src/main/java/hudson/plugins/repo/TagAction.java +++ b/src/main/java/hudson/plugins/repo/TagAction.java @@ -23,26 +23,35 @@ */ package hudson.plugins.repo; -import hudson.model.AbstractBuild; -import hudson.scm.AbstractScmTagAction; +import java.io.ObjectStreamException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; -import org.kohsuke.stapler.export.ExportedBean; +import hudson.model.Run; +import hudson.scm.AbstractScmTagAction; -/** + /** * A Tag Action allows a user to tag a build. Repo doesn't support a solid tag * method, so right now we just display the static manifest information needed * to recreate the exact state of the repository when the build was ran. + * @deprecated replaced by {@link ManifestAction} JENKINS-59923 */ -@ExportedBean(defaultVisibility = 999) +@Deprecated +@Restricted(NoExternalUse.class) public class TagAction extends AbstractScmTagAction { + private static Logger debug = Logger.getLogger("hudson.plugins.repo.TagAction"); + /** * Constructs the tag action object. Just call the superclass. * * @param build * Build which we are interested in tagging */ - TagAction(final AbstractBuild build) { + TagAction(final Run build) { super(build); } @@ -54,7 +63,7 @@ public String getIconFileName() { // TODO: return null if we don't want to show a link (no permissions?) // TODO: if we later support actual tagging, we can use star-gold.gif // for already tagged builds - return "star.gif"; + return null; } /** @@ -63,7 +72,7 @@ public String getIconFileName() { */ public String getDisplayName() { // TODO: adjust name based on build state (tagged already or not)? - return "Repo Manifest"; + return null; } @Override @@ -83,9 +92,20 @@ public boolean isTagged() { * snapshot. */ public String getManifest() { - final RevisionState revisionState = - getBuild().getAction(RevisionState.class); - final String manifest = revisionState.getManifest(); - return manifest; + return null; + } + + @Override + public void onAttached(final Run r) { + debug.log(Level.SEVERE, "Unexpected attach of TagAction class"); + } + + /** + * Migrate to a new ManifestAction. + * @return + * @throws ObjectStreamException if there is an issue + */ + Object readResolve() throws ObjectStreamException { + return new ManifestAction(build); } } diff --git a/src/main/resources/hudson/plugins/repo/ManifestAction/badge.jelly b/src/main/resources/hudson/plugins/repo/ManifestAction/badge.jelly new file mode 100644 index 0000000..49d23dc --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/ManifestAction/badge.jelly @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/hudson/plugins/repo/ManifestAction/index.jelly b/src/main/resources/hudson/plugins/repo/ManifestAction/index.jelly new file mode 100644 index 0000000..fa16f9e --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/ManifestAction/index.jelly @@ -0,0 +1,28 @@ + + + + + + + + +

Repo Manifest - Build #${it.run.number}

+ To recreate this build, copy the below manifest and past it to .repo/manifest.xml, then run 'repo sync'. + When done, be sure to undo changes to the .repo/manifest.xml file and repo sync again. +

+ Manifest File:

+ + Generated from ${manifest.file} in branch ${manifest.branch} of ${manifest.url}. +
+ +

+
+
+
+
\ No newline at end of file diff --git a/src/main/resources/hudson/plugins/repo/RepoChangeLogSet/digest.jelly b/src/main/resources/hudson/plugins/repo/RepoChangeLogSet/digest.jelly index aa7caf2..b702c63 100644 --- a/src/main/resources/hudson/plugins/repo/RepoChangeLogSet/digest.jelly +++ b/src/main/resources/hudson/plugins/repo/RepoChangeLogSet/digest.jelly @@ -1,3 +1,4 @@ +
  • - ${cs.msgAnnotated}
    - -- ${cs.author} / - detail
    + — + + + ${cs.author} + + + ${cs.path} + + + / detail
  • diff --git a/src/main/resources/hudson/plugins/repo/RepoChangeLogSet/index.jelly b/src/main/resources/hudson/plugins/repo/RepoChangeLogSet/index.jelly index 1ebfe8b..e9f5c0e 100644 --- a/src/main/resources/hudson/plugins/repo/RepoChangeLogSet/index.jelly +++ b/src/main/resources/hudson/plugins/repo/RepoChangeLogSet/index.jelly @@ -1,3 +1,4 @@ + Project: ${cs.path}
    - Revision: ${cs.revision}
    - Author: ${cs.author} - &lt;${cs.authorEmail}&gt; on ${cs.authorDate}
    - Committer: ${cs.committer} - &lt;${cs.committerEmail}&gt; on ${cs.committerDate}
    -

    + + Revision: ${cs.revision}
    +
    + + Author: ${cs.author} + &lt;${cs.authorEmail}&gt; on ${cs.authorDate}
    + + + Committer: ${cs.committer} + &lt;${cs.committerEmail}&gt; on ${cs.committerDate}
    +
    +
    - + diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/buildEnv.groovy b/src/main/resources/hudson/plugins/repo/RepoScm/buildEnv.groovy new file mode 100644 index 0000000..5936788 --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/buildEnv.groovy @@ -0,0 +1,9 @@ +package hudson.plugins.repo.RepoSCM + +def l = namespace(lib.JenkinsTagLib) + +['REPO_MANIFEST_URL', 'REPO_MANIFEST_BRANCH', 'REPO_MANIFEST_FILE', 'REPO_MANIFEST_XML'].each {name -> + l.buildEnvVar(name: name) { + raw(_("${name}.blurb")) + } +} diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/buildEnv.properties b/src/main/resources/hudson/plugins/repo/RepoScm/buildEnv.properties new file mode 100644 index 0000000..2b04d4f --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/buildEnv.properties @@ -0,0 +1,5 @@ +REPO_MANIFEST_URL.blurb=The URL of manifest repository used. +REPO_MANIFEST_BRANCH.blurb=The branch of the manifest repository used. +REPO_MANIFEST_FILE.blurb=The manifest filename used. +REPO_MANIFEST_XML.blurb=The static manifest (in XML format), if manifest size is greater \ + than MAX_ARG_STRLEN (131072 bytes), value will be set to a single underscore character. diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/config.jelly b/src/main/resources/hudson/plugins/repo/RepoScm/config.jelly index 6512e8d..ada0c9d 100644 --- a/src/main/resources/hudson/plugins/repo/RepoScm/config.jelly +++ b/src/main/resources/hudson/plugins/repo/RepoScm/config.jelly @@ -1,3 +1,4 @@ + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + + + + diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/global.jelly b/src/main/resources/hudson/plugins/repo/RepoScm/global.jelly index a40ba8b..333a20c 100644 --- a/src/main/resources/hudson/plugins/repo/RepoScm/global.jelly +++ b/src/main/resources/hudson/plugins/repo/RepoScm/global.jelly @@ -1,3 +1,4 @@ + + checkUrl="${rootURL}/scm/RepoScm/executableCheck" checkDependsOn=""/> \ No newline at end of file diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-cleanFirst.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-cleanFirst.html new file mode 100644 index 0000000..a5683cb --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-cleanFirst.html @@ -0,0 +1,5 @@ +
    +

    + When this is checked the first thing to do will be a

    repo forall -c "git clean -fdx"
    +

    +
    diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-currentBranch.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-currentBranch.html new file mode 100644 index 0000000..e91bfca --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-currentBranch.html @@ -0,0 +1,7 @@ +
    +

    + Fetch only the current branch from server. + Increases the speed of the repo sync operation. + This is passed to repo as repo init --current-branch and repo sync -c. +

    +
    diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-depth.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-depth.html new file mode 100644 index 0000000..1fed342 --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-depth.html @@ -0,0 +1,7 @@ +
    +

    + Specify the depth in history to sync from the source. The default is to sync all of the history. + Use 1 to just sync the most recent commit. + This is passed to repo as repo init --depth=n. +

    +
    diff --git a/src/main/webapp/help-destinationDir.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-destinationDir.html similarity index 100% rename from src/main/webapp/help-destinationDir.html rename to src/main/resources/hudson/plugins/repo/RepoScm/help-destinationDir.html diff --git a/src/main/webapp/help-executable.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-executable.html similarity index 66% rename from src/main/webapp/help-executable.html rename to src/main/resources/hudson/plugins/repo/RepoScm/help-executable.html index ca07b2f..36abadd 100644 --- a/src/main/webapp/help-executable.html +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-executable.html @@ -1,10 +1,10 @@

    This is the path to the repo executable which will be called whenever repo commands - are ran. Repo can be installed by running curl https://android.git.kernel.org/repo > ~/bin/repo + are ran. Repo can be installed by running curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo

    See more documentation about installing - and using repo. + and using repo.

    diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-fetchSubmodules.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-fetchSubmodules.html new file mode 100644 index 0000000..6527f04 --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-fetchSubmodules.html @@ -0,0 +1,6 @@ +
    +

    + Fetch submodules for from server. + This is passed to repo as repo sync --fetch-submodules. +

    +
    diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-forceSync.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-forceSync.html new file mode 100644 index 0000000..0e91f52 --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-forceSync.html @@ -0,0 +1,7 @@ +
    +

    + Overwrite an existing git directory if it needs to point to a different + object directory. WARNING: this may cause loss of data. + This is passed to repo as repo sync --force-sync. +

    +
    diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-gitLfs.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-gitLfs.html new file mode 100644 index 0000000..f3e6aaa --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-gitLfs.html @@ -0,0 +1,6 @@ +
    +

    + Allow repositories utilizing LFS to sync LFS objects. + This is passed to repo as repo init --git-lfs. +

    +
    diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-ignoreChanges.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-ignoreChanges.html new file mode 100644 index 0000000..f358ce9 --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-ignoreChanges.html @@ -0,0 +1,6 @@ +
    +

    + Specify projects changes in which would not be considered a change that requires project rebuild when polling scm. + Project should be specified as the name in the name section of the project declaration in manifest separated by spaces. +

    +
    diff --git a/src/main/webapp/help-jobs.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-jobs.html similarity index 100% rename from src/main/webapp/help-jobs.html rename to src/main/resources/hudson/plugins/repo/RepoScm/help-jobs.html diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-localManifest.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-localManifest.html new file mode 100644 index 0000000..a49b1af --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-localManifest.html @@ -0,0 +1,19 @@ +
    +

    + The contents of .repo/local_manifests/local.xml. This is written prior to +calling sync. The default is to not use a local.xml file. +

    +

    The contents may be given here literally, as XML; see the example below. +Such literal content must start with the +string <?xml. Alternatively, the content may be given +as an URL, in which case the file pointed by the URL is used. If the +content does not start with the <?xml prefix, it is +assumed to be an URL.

    +

    An example

    +
    +    <?xml version="1.0" encoding="UTF-8"?>
    +    <manifest>
    +      <project path="external/project" name="org/project" remote="github" revision="master" />
    +    </manifest>
    +  
    +
    diff --git a/src/main/webapp/help-manifestBranch.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-manifestBranch.html similarity index 100% rename from src/main/webapp/help-manifestBranch.html rename to src/main/resources/hudson/plugins/repo/RepoScm/help-manifestBranch.html diff --git a/src/main/webapp/help-manifestFile.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-manifestFile.html similarity index 100% rename from src/main/webapp/help-manifestFile.html rename to src/main/resources/hudson/plugins/repo/RepoScm/help-manifestFile.html diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-manifestGroup.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-manifestGroup.html new file mode 100644 index 0000000..35d366e --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-manifestGroup.html @@ -0,0 +1,7 @@ +
    +

    + Restricts manifest projects to ones tagged with provided group name. This is passed to repo as + repo init -g groupName. If a group name is not provided, the -g + option is not passed to repo and it will default to fetching projects that are tagged with 'default'. +

    +
    diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-manifestPlatform.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-manifestPlatform.html new file mode 100644 index 0000000..9b92243 --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-manifestPlatform.html @@ -0,0 +1,8 @@ +
    +

    + Restrict manifest projects to ones with a specified platform group [auto|all|none|linux|darwin|...] + This is passed to repo as repo init -P platformName. If a platform is not provided, the + -p option is not passed to repo and it will default to auto and ony fetch projects + which needed for current system. +

    +
    diff --git a/src/main/webapp/help-manifestRepositoryUrl.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-manifestRepositoryUrl.html similarity index 100% rename from src/main/webapp/help-manifestRepositoryUrl.html rename to src/main/resources/hudson/plugins/repo/RepoScm/help-manifestRepositoryUrl.html diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-manifestSubmodules.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-manifestSubmodules.html new file mode 100644 index 0000000..93cdf44 --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-manifestSubmodules.html @@ -0,0 +1,6 @@ +
    +

    + Sync any submodules associated with the manifest repo. + This is passed to repo as repo init --submodules. +

    +
    diff --git a/src/main/webapp/help-mirrorDir.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-mirrorDir.html similarity index 100% rename from src/main/webapp/help-mirrorDir.html rename to src/main/resources/hudson/plugins/repo/RepoScm/help-mirrorDir.html diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-noCloneBundle.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-noCloneBundle.html new file mode 100644 index 0000000..8f7cbba --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-noCloneBundle.html @@ -0,0 +1,6 @@ +
    +

    + When this is checked --no-clone-bundle is used when running + the repo init and repo sync commands. +

    +
    diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-noTags.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-noTags.html new file mode 100644 index 0000000..a3f1604 --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-noTags.html @@ -0,0 +1,6 @@ +
    +

    + Don't fetch tags. + This is passed to repo as repo init --no-tags and repo sync --no-tags. +

    +
    diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-quiet.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-quiet.html new file mode 100644 index 0000000..097aa73 --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-quiet.html @@ -0,0 +1,6 @@ +
    +

    + Make repo more quiet. + This is passed to repo as repo sync -q. +

    +
    diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-repoBranch.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-repoBranch.html new file mode 100644 index 0000000..b2277b5 --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-repoBranch.html @@ -0,0 +1,6 @@ +
    +

    + Use a specific branch for pulling repo itself. By default this is empty, and repo will be using its + default branch (i.e. stable) +

    +
    diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-repoUrl.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-repoUrl.html new file mode 100644 index 0000000..20c90c9 --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-repoUrl.html @@ -0,0 +1,6 @@ +
    +

    + Pull repo itself from this git repository. By default this is empty, and repo will be pulled + from its default git url (i.e. googles) +

    +
    diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-resetFirst.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-resetFirst.html new file mode 100644 index 0000000..2443d42 --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-resetFirst.html @@ -0,0 +1,5 @@ +
    +

    + When this is checked the first thing to do will be a

    repo forall -c "git reset --hard"
    +

    +
    diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-showAllChanges.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-showAllChanges.html new file mode 100644 index 0000000..18bcd6f --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-showAllChanges.html @@ -0,0 +1,6 @@ +
    +

    + When this is checked --first-parent is no longer passed + to git log when determining changesets. +

    +
    diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-trace.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-trace.html new file mode 100644 index 0000000..04eb903 --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-trace.html @@ -0,0 +1,6 @@ +
    +

    + Trace git command execution. This is passed to repo as + repo --trace <subcommand>. +

    +
    diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help-worktree.html b/src/main/resources/hudson/plugins/repo/RepoScm/help-worktree.html new file mode 100644 index 0000000..f2464f3 --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help-worktree.html @@ -0,0 +1,5 @@ +
    +

    + Use `git worktree` for checkouts (At least, Git version 2.15 is required to avoid dangerous gc bugs). Usefull under Windows because it no longer require symlinks at all. +

    +
    diff --git a/src/main/resources/hudson/plugins/repo/RepoScm/help.html b/src/main/resources/hudson/plugins/repo/RepoScm/help.html new file mode 100644 index 0000000..b84d890 --- /dev/null +++ b/src/main/resources/hudson/plugins/repo/RepoScm/help.html @@ -0,0 +1,11 @@ +
    +

    + The repo plugin provides Repo as an SCM tools in Jenkins. +

    + +

    + The repo plugin provides an SCM implementation to be used with the Pipeline SCM checkout step. + The Pipeline Syntax Snippet Generator guides the user to select repo plugin checkout options and provides online help for each of the options. +

    + +
    diff --git a/src/main/resources/hudson/plugins/repo/TagAction/badge.jelly b/src/main/resources/hudson/plugins/repo/TagAction/badge.jelly deleted file mode 100644 index 1e0fcb2..0000000 --- a/src/main/resources/hudson/plugins/repo/TagAction/badge.jelly +++ /dev/null @@ -1,10 +0,0 @@ - - - - [tagged] - - - \ No newline at end of file diff --git a/src/main/resources/hudson/plugins/repo/TagAction/tagForm.jelly b/src/main/resources/hudson/plugins/repo/TagAction/tagForm.jelly deleted file mode 100644 index f701fee..0000000 --- a/src/main/resources/hudson/plugins/repo/TagAction/tagForm.jelly +++ /dev/null @@ -1,20 +0,0 @@ - - - - - -

    Repo Manifest - Build #${it.build.number}

    - To recreate this build, copy the below manifest and past it to .repo/manifest.xml, then run 'repo sync'. - When done, be sure to undo changes to the .repo/manifest.xml file and repo sync again. - - Manifest File:

    - -
    -
    -
    \ No newline at end of file diff --git a/src/main/resources/index.jelly b/src/main/resources/index.jelly index c2561e6..b6a6189 100644 --- a/src/main/resources/index.jelly +++ b/src/main/resources/index.jelly @@ -1,4 +1,5 @@ +
    - This plugin allows use of repo as - an SCM tool. A repo binary is required. + This plugin allows use of Repo as + an SCM tool. A repo binary is required.
    \ No newline at end of file diff --git a/src/main/webapp/help-localManifest.html b/src/main/webapp/help-localManifest.html deleted file mode 100644 index 1ce40a4..0000000 --- a/src/main/webapp/help-localManifest.html +++ /dev/null @@ -1,6 +0,0 @@ -
    -

    - The contents of .repo/local_manifest.xml. This is written prior to -callinging sync. The default is to not use a local_manifest file. -

    -
    diff --git a/src/test/java/hudson/plugins/repo/ManifestValidatorTest.java b/src/test/java/hudson/plugins/repo/ManifestValidatorTest.java new file mode 100644 index 0000000..6dbd84a --- /dev/null +++ b/src/test/java/hudson/plugins/repo/ManifestValidatorTest.java @@ -0,0 +1,56 @@ +package hudson.plugins.repo; + +import org.junit.Test; +import org.jvnet.hudson.test.Issue; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.fail; + +public class ManifestValidatorTest { + + @Issue("SECURITY-2478") + @Test + public void validateWhenFetchAttributeReferencesLocalPathThenAbort() { + String manifest = "\n" + + "\n" + + " \n" + + "\n" + + " \n" + + ""; + try { + ManifestValidator.validate(manifest.getBytes(StandardCharsets.UTF_8), "repoUrl"); + fail("should fail because fetch attribute in remote tag references a local path"); + } catch (IOException e) { + assertThat(e.getMessage(), is("Checkout of Repo url 'repoUrl' aborted because manifest references a local directory, " + + "which may be insecure. You can allow local checkouts anyway by setting the system property '" + + RepoScm.ALLOW_LOCAL_CHECKOUT_PROPERTY + "' to true.")); + } + } + + @Issue("SECURITY-2478") + @Test + public void validateWhenValidManifestThenDoNotAbort() { + String manifest = "\n" + + "\n" + + " \n" + + "\n" + + " \n" + + " "; + + try { + ManifestValidator.validate(manifest.getBytes(StandardCharsets.UTF_8), "repoUrl"); + } catch (Exception e) { + fail("fail because input is valid and no exception expected"); + } + } +} diff --git a/src/test/java/hudson/plugins/repo/RepoScmTest.java b/src/test/java/hudson/plugins/repo/RepoScmTest.java new file mode 100644 index 0000000..88527aa --- /dev/null +++ b/src/test/java/hudson/plugins/repo/RepoScmTest.java @@ -0,0 +1,34 @@ +package hudson.plugins.repo; + +import hudson.model.FreeStyleProject; +import hudson.tasks.Shell; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * {@link JenkinsRule} based tests for {@link RepoScm} + */ +public class RepoScmTest { + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + public void configRoundTrip() throws Exception { + FreeStyleProject project = j.createFreeStyleProject(); + final String manifestRepositoryUrl = "https://gerrit/projects/platform.git"; + RepoScm scm = new RepoScm(manifestRepositoryUrl); + scm.setCleanFirst(true); + project.setScm(scm); + project.getBuildersList().add(new Shell("ecgo hello")); + project.save(); + j.configRoundtrip(project); + project = j.jenkins.getItemByFullName(project.getFullName(), FreeStyleProject.class); + scm = (RepoScm) project.getScm(); + assertTrue(scm.isCleanFirst()); + assertEquals(manifestRepositoryUrl, scm.getManifestRepositoryUrl()); + } +} diff --git a/src/test/java/hudson/plugins/repo/Security2478Test.java b/src/test/java/hudson/plugins/repo/Security2478Test.java new file mode 100644 index 0000000..d2f8637 --- /dev/null +++ b/src/test/java/hudson/plugins/repo/Security2478Test.java @@ -0,0 +1,76 @@ +package hudson.plugins.repo; + +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.model.Result; +import hudson.slaves.DumbSlave; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; + +public class Security2478Test { + + @Rule + public JenkinsRule rule = new JenkinsRule(); + + @Rule + public TemporaryFolder testFolder = new TemporaryFolder(); + + @Issue("SECURITY-2478") + @Test + public void checkoutShouldAbortWhenUrlIsNonRemoteAndBuildOnController() throws Exception { + FreeStyleProject freeStyleProject = rule.createFreeStyleProject(); + String manifestRepositoryUrl = testFolder.newFolder().toString(); + RepoScm scm = new RepoScm(manifestRepositoryUrl); + freeStyleProject.setScm(scm); + FreeStyleBuild freeStyleBuild = rule.assertBuildStatus(Result.FAILURE, freeStyleProject.scheduleBuild2(0)); + rule.assertLogContains("Checkout of Repo url '" + manifestRepositoryUrl + + "' aborted because it references a local directory, " + + "which may be insecure. You can allow local checkouts anyway by setting the system property '" + + RepoScm.ALLOW_LOCAL_CHECKOUT_PROPERTY + "' to true.", freeStyleBuild); + } + + @Issue("SECURITY-2478") + @Test + public void checkoutShouldNotAbortWhenUrlIsNonRemoteAndEscapeHatchTrue() throws Exception { + try { + RepoScm.ALLOW_LOCAL_CHECKOUT = true; + FreeStyleProject freeStyleProject = rule.createFreeStyleProject(); + String manifestRepositoryUrl = testFolder.newFolder().toString(); + RepoScm scm = new RepoScm(manifestRepositoryUrl); + freeStyleProject.setScm(scm); + FreeStyleBuild freeStyleBuild = rule.assertBuildStatus(Result.FAILURE, freeStyleProject.scheduleBuild2(0)); + + // build fails because of manifestRepositoryUrl is not a repo(git) repository, but we don't care, + // we verify that build was not aborted because of RepoScm uses local path. + rule.assertLogNotContains("Checkout of Repo url '" + manifestRepositoryUrl + + "' aborted because it references a local directory, " + + "which may be insecure. You can allow local checkouts anyway by setting the system property '" + + RepoScm.ALLOW_LOCAL_CHECKOUT_PROPERTY + "' to true.", freeStyleBuild); + } finally { + RepoScm.ALLOW_LOCAL_CHECKOUT = false; + } + } + + @Issue("SECURITY-2478") + @Test + public void checkoutShouldNotAbortWhenUrlIsNonRemoteAndBuildOnAgent() throws Exception { + DumbSlave agent = rule.createOnlineSlave(); + FreeStyleProject freeStyleProject = rule.createFreeStyleProject(); + + String manifestRepositoryUrl = testFolder.newFolder().toString(); + + RepoScm scm = new RepoScm(manifestRepositoryUrl); + freeStyleProject.setScm(scm); + freeStyleProject.setAssignedLabel(agent.getSelfLabel()); + + // build fails because of manifestRepositoryUrl is not a repo(git) repository, but we don't care, + // we verify that build was not aborted because of RepoScm uses local path. + rule.assertLogNotContains("Checkout of Repo url '" + manifestRepositoryUrl + + "' aborted because it references a local directory, " + + "which may be insecure. You can allow local checkouts anyway by setting the system property '" + + RepoScm.ALLOW_LOCAL_CHECKOUT_PROPERTY + "' to true.", freeStyleProject.scheduleBuild2(0).get()); + } +} diff --git a/src/test/java/hudson/plugins/repo/TestProjectState.java b/src/test/java/hudson/plugins/repo/TestProjectState.java new file mode 100644 index 0000000..d6ead42 --- /dev/null +++ b/src/test/java/hudson/plugins/repo/TestProjectState.java @@ -0,0 +1,68 @@ +/* + * The MIT License + * + * Copyright (c) 2011, Brad Larson + * + * 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 hudson.plugins.repo; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; + +import junit.framework.TestCase; + +/** + * Test cases for the {@link RevisionState} class. + */ +public class TestProjectState extends TestCase { + + private ProjectState projectStateA = ProjectState.constructCachedInstance("a", "a", + "c9039e9649d133d80073e432816b9b4915776b41"); + + + private ProjectState projectStateB = ProjectState.constructCachedInstance("b", "b", + "fa822eff984195ec8923718cd025fd44b77a26ef"); + + private ProjectState projectStateA2 = ProjectState.constructCachedInstance("a", "a", + "c9039e9649d133d80073e432816b9b4915776b41"); + + + /** + * Test {@link ProjectState#constructCachedInstance(String, String, String)} + */ + public void testCaching() + { + Assert.assertTrue(projectStateA == projectStateA2); + Assert.assertFalse(projectStateA == projectStateB); + Assert.assertFalse(projectStateB == projectStateA2); + } + + /** + * Test {@link ProjectState#equals(Object)}. + */ + public void testEquality() + { + Assert.assertTrue(projectStateA.equals(projectStateA2)); + Assert.assertFalse(projectStateA.equals(projectStateB)); + Assert.assertFalse(projectStateB.equals(projectStateA2)); + } +} diff --git a/src/test/java/hudson/plugins/repo/TestRepoScm.java b/src/test/java/hudson/plugins/repo/TestRepoScm.java new file mode 100644 index 0000000..d9c1bbb --- /dev/null +++ b/src/test/java/hudson/plugins/repo/TestRepoScm.java @@ -0,0 +1,74 @@ +/* + * The MIT License + * + * Copyright (c) 2011, Brad Larson + * + * 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 hudson.plugins.repo; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Assert; + +import junit.framework.TestCase; + +/** + * Test cases for the {@link RevisionState} class. + */ +public class TestRepoScm extends TestCase { + + + public void testSetIgnoredProjects() { + RepoScm scm = new RepoScm("http://manifesturl"); + scm.setIgnoreProjects(""); + assertEquals("", scm.getIgnoreProjects()); + + } + + public void testSetIgnoredProjectsKeepsOrder() { + RepoScm scm = new RepoScm("http://manifesturl"); + scm.setIgnoreProjects("projecta projectb"); + assertEquals("projecta\nprojectb", scm.getIgnoreProjects()); + scm.setIgnoreProjects("projectb projecta"); + assertEquals("projectb\nprojecta", scm.getIgnoreProjects()); + } + + public void testResetFirst() { + RepoScm scm = new RepoScm("http://manifesturl"); + assertEquals(false, scm.isResetFirst()); + scm.setResetFirst(true); + assertEquals(true, scm.isResetFirst()); + } + + public void testCleanFirst() { + RepoScm scm = new RepoScm("http://manifesturl"); + assertEquals(false, scm.isCleanFirst()); + scm.setCleanFirst(true); + assertEquals(true, scm.isCleanFirst()); + } + + public void testWorktree() { + RepoScm scm = new RepoScm("http://manifesturl"); + assertEquals(false, scm.isWorktree()); + scm.setWorktree(true); + assertEquals(true, scm.isWorktree()); + } +} diff --git a/src/test/java/hudson/plugins/repo/TestRevisionState.java b/src/test/java/hudson/plugins/repo/TestRevisionState.java index e152d0d..271825a 100644 --- a/src/test/java/hudson/plugins/repo/TestRevisionState.java +++ b/src/test/java/hudson/plugins/repo/TestRevisionState.java @@ -40,6 +40,8 @@ public class TestRevisionState extends TestCase { private RevisionState stateOneCopy; private RevisionState stateTwo; private RevisionState stateThree; + private RevisionState stateMChange; + private String manifestOne = "" @@ -72,10 +74,12 @@ public class TestRevisionState extends TestCase { protected void setUp() throws Exception { super.setUp(); - stateOne = new RevisionState(manifestOne, "master", null); - stateOneCopy = new RevisionState(manifestOne, "master", null); - stateTwo = new RevisionState(manifestTwo, "master", null); - stateThree = new RevisionState(manifestThree, "master", null); + stateOne = new RevisionState(manifestOne, "a", "https://my.gerrit.com/myrepo", "master", "default.xml", null); + stateOneCopy = new RevisionState(manifestOne, "a", "https://my.gerrit.com/myrepo", "master", "default.xml", null); + stateTwo = new RevisionState(manifestTwo, "a", "https://my.gerrit.com/myrepo", "master", "default.xml", null); + stateThree = new RevisionState(manifestThree, "a", "https://my.gerrit.com/myrepo", "master", "default.xml", null); + + stateMChange = new RevisionState(manifestThree, "b", "https://my.gerrit.com/myrepo", "master", "default.xml", null); } /** @@ -85,6 +89,7 @@ public void testEquality() { Assert.assertTrue(stateOne.equals(stateOneCopy)); Assert.assertFalse(stateOne.equals(stateTwo)); Assert.assertFalse(stateTwo.equals(stateThree)); + Assert.assertFalse(stateThree.equals(stateMChange)); } /** @@ -95,15 +100,15 @@ public void testChangeDetection() { List changes = stateTwo.whatChanged(stateOne); List expectedChanges = new ArrayList(); - expectedChanges.add(new ProjectState("a", "a", "c9039e9649d133d80073e432816b9b4915776b41")); - expectedChanges.add(new ProjectState("c", "c", "fa822eff984195ec8923718cd025fd44b77a26ef")); - expectedChanges.add(new ProjectState("d", "d", null)); + expectedChanges.add(ProjectState.constructCachedInstance("a", "a", "c9039e9649d133d80073e432816b9b4915776b41")); + expectedChanges.add(ProjectState.constructCachedInstance("c", "c", "fa822eff984195ec8923718cd025fd44b77a26ef")); + expectedChanges.add(ProjectState.constructCachedInstance("d", "d", null)); Assert.assertEquals(expectedChanges, changes); changes = stateThree.whatChanged(stateTwo); expectedChanges.clear(); - expectedChanges.add(new ProjectState("b", "b", "c27d6b02c859b291878db67f256cefac3adb26df")); - expectedChanges.add(new ProjectState("c", "c", "7086d7305fa6c7c1930de1e7d96fffc9c819b479")); + expectedChanges.add(ProjectState.constructCachedInstance("b", "b", "c27d6b02c859b291878db67f256cefac3adb26df")); + expectedChanges.add(ProjectState.constructCachedInstance("c", "c", "7086d7305fa6c7c1930de1e7d96fffc9c819b479")); Assert.assertEquals(expectedChanges, changes); } }