Initial EGit pipeline library

Initial content from the proof of concept. README.md expanded with
some explanations.

Bug: 548214
Change-Id: I93f66ef317867221436fa9d351b732b3ae231ed8
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..9277ff6
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+	<classpathentry kind="con" path="GROOVY_DSL_SUPPORT"/>
+	<classpathentry kind="con" path="GROOVY_SUPPORT"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="vars"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fb60f67
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+bin/
+target/
diff --git a/.project b/.project
new file mode 100644
index 0000000..1d512f1
--- /dev/null
+++ b/.project
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>egit-pipelines</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.groovy.core.groovyNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..3a21537
--- /dev/null
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/.settings/org.eclipse.jdt.groovy.core.prefs b/.settings/org.eclipse.jdt.groovy.core.prefs
new file mode 100644
index 0000000..74af1ba
--- /dev/null
+++ b/.settings/org.eclipse.jdt.groovy.core.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+groovy.compiler.level=25
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e23ece2
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,277 @@
+Eclipse Public License - v 2.0
+
+    THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+    PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION
+    OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+  a) in the case of the initial Contributor, the initial content
+     Distributed under this Agreement, and
+
+  b) in the case of each subsequent Contributor:
+     i) changes to the Program, and
+     ii) additions to the Program;
+  where such changes and/or additions to the Program originate from
+  and are Distributed by that particular Contributor. A Contribution
+  "originates" from a Contributor if it was added to the Program by
+  such Contributor itself or anyone acting on such Contributor's behalf.
+  Contributions do not include changes or additions to the Program that
+  are not Modified Works.
+
+"Contributor" means any person or entity that Distributes the Program.
+
+"Licensed Patents" mean patent claims licensable by a Contributor which
+are necessarily infringed by the use or sale of its Contribution alone
+or when combined with the Program.
+
+"Program" means the Contributions Distributed in accordance with this
+Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement
+or any Secondary License (as applicable), including Contributors.
+
+"Derivative Works" shall mean any work, whether in Source Code or other
+form, that is based on (or derived from) the Program and for which the
+editorial revisions, annotations, elaborations, or other modifications
+represent, as a whole, an original work of authorship.
+
+"Modified Works" shall mean any work in Source Code or other form that
+results from an addition to, deletion from, or modification of the
+contents of the Program, including, for purposes of clarity any new file
+in Source Code form that contains any contents of the Program. Modified
+Works shall not include works that contain only declarations,
+interfaces, types, classes, structures, or files of the Program solely
+in each case in order to link to, bind by name, or subclass the Program
+or Modified Works thereof.
+
+"Distribute" means the acts of a) distributing or b) making available
+in any manner that enables the transfer of a copy.
+
+"Source Code" means the form of a Program preferred for making
+modifications, including but not limited to software source code,
+documentation source, and configuration files.
+
+"Secondary License" means either the GNU General Public License,
+Version 2.0, or any later versions of that license, including any
+exceptions or additional permissions as identified by the initial
+Contributor.
+
+2. GRANT OF RIGHTS
+
+  a) Subject to the terms of this Agreement, each Contributor hereby
+  grants Recipient a non-exclusive, worldwide, royalty-free copyright
+  license to reproduce, prepare Derivative Works of, publicly display,
+  publicly perform, Distribute and sublicense the Contribution of such
+  Contributor, if any, and such Derivative Works.
+
+  b) Subject to the terms of this Agreement, each Contributor hereby
+  grants Recipient a non-exclusive, worldwide, royalty-free patent
+  license under Licensed Patents to make, use, sell, offer to sell,
+  import and otherwise transfer the Contribution of such Contributor,
+  if any, in Source Code or other form. This patent license shall
+  apply to the combination of the Contribution and the Program if, at
+  the time the Contribution is added by the Contributor, such addition
+  of the Contribution causes such combination to be covered by the
+  Licensed Patents. The patent license shall not apply to any other
+  combinations which include the Contribution. No hardware per se is
+  licensed hereunder.
+
+  c) Recipient understands that although each Contributor grants the
+  licenses to its Contributions set forth herein, no assurances are
+  provided by any Contributor that the Program does not infringe the
+  patent or other intellectual property rights of any other entity.
+  Each Contributor disclaims any liability to Recipient for claims
+  brought by any other entity based on infringement of intellectual
+  property rights or otherwise. As a condition to exercising the
+  rights and licenses granted hereunder, each Recipient hereby
+  assumes sole responsibility to secure any other intellectual
+  property rights needed, if any. For example, if a third party
+  patent license is required to allow Recipient to Distribute the
+  Program, it is Recipient's responsibility to acquire that license
+  before distributing the Program.
+
+  d) Each Contributor represents that to its knowledge it has
+  sufficient copyright rights in its Contribution, if any, to grant
+  the copyright license set forth in this Agreement.
+
+  e) Notwithstanding the terms of any Secondary License, no
+  Contributor makes additional grants to any Recipient (other than
+  those set forth in this Agreement) as a result of such Recipient's
+  receipt of the Program under the terms of a Secondary License
+  (if permitted under the terms of Section 3).
+
+3. REQUIREMENTS
+
+3.1 If a Contributor Distributes the Program in any form, then:
+
+  a) the Program must also be made available as Source Code, in
+  accordance with section 3.2, and the Contributor must accompany
+  the Program with a statement that the Source Code for the Program
+  is available under this Agreement, and informs Recipients how to
+  obtain it in a reasonable manner on or through a medium customarily
+  used for software exchange; and
+
+  b) the Contributor may Distribute the Program under a license
+  different than this Agreement, provided that such license:
+     i) effectively disclaims on behalf of all other Contributors all
+     warranties and conditions, express and implied, including
+     warranties or conditions of title and non-infringement, and
+     implied warranties or conditions of merchantability and fitness
+     for a particular purpose;
+
+     ii) effectively excludes on behalf of all other Contributors all
+     liability for damages, including direct, indirect, special,
+     incidental and consequential damages, such as lost profits;
+
+     iii) does not attempt to limit or alter the recipients' rights
+     in the Source Code under section 3.2; and
+
+     iv) requires any subsequent distribution of the Program by any
+     party to be under a license that satisfies the requirements
+     of this section 3.
+
+3.2 When the Program is Distributed as Source Code:
+
+  a) it must be made available under this Agreement, or if the
+  Program (i) is combined with other material in a separate file or
+  files made available under a Secondary License, and (ii) the initial
+  Contributor attached to the Source Code the notice described in
+  Exhibit A of this Agreement, then the Program may be made available
+  under the terms of such Secondary Licenses, and
+
+  b) a copy of this Agreement must be included with each copy of
+  the Program.
+
+3.3 Contributors may not remove or alter any copyright, patent,
+trademark, attribution notices, disclaimers of warranty, or limitations
+of liability ("notices") contained within the Program from any copy of
+the Program which they Distribute, provided that Contributors may add
+their own appropriate notices.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities
+with respect to end users, business partners and the like. While this
+license is intended to facilitate the commercial use of the Program,
+the Contributor who includes the Program in a commercial product
+offering should do so in a manner which does not create potential
+liability for other Contributors. Therefore, if a Contributor includes
+the Program in a commercial product offering, such Contributor
+("Commercial Contributor") hereby agrees to defend and indemnify every
+other Contributor ("Indemnified Contributor") against any losses,
+damages and costs (collectively "Losses") arising from claims, lawsuits
+and other legal actions brought by a third party against the Indemnified
+Contributor to the extent caused by the acts or omissions of such
+Commercial Contributor in connection with its distribution of the Program
+in a commercial product offering. The obligations in this section do not
+apply to any claims or Losses relating to any actual or alleged
+intellectual property infringement. In order to qualify, an Indemnified
+Contributor must: a) promptly notify the Commercial Contributor in
+writing of such claim, and b) allow the Commercial Contributor to control,
+and cooperate with the Commercial Contributor in, the defense and any
+related settlement negotiations. The Indemnified Contributor may
+participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial
+product offering, Product X. That Contributor is then a Commercial
+Contributor. If that Commercial Contributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Contributor's responsibility
+alone. Under this section, the Commercial Contributor would have to
+defend claims against the other Contributors related to those performance
+claims and warranties, and if a court requires any other Contributor to
+pay any damages as a result, the Commercial Contributor must pay
+those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
+PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS"
+BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
+IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF
+TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
+PURPOSE. Each Recipient is solely responsible for determining the
+appropriateness of using and distributing the Program and assumes all
+risks associated with its exercise of rights under this Agreement,
+including but not limited to the risks and costs of program errors,
+compliance with applicable laws, damage to or loss of data, programs
+or equipment, and unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
+PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS
+SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
+PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
+EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further
+action by the parties hereto, such provision shall be reformed to the
+minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity
+(including a cross-claim or counterclaim in a lawsuit) alleging that the
+Program itself (excluding combinations of the Program with other software
+or hardware) infringes such Recipient's patent(s), then such Recipient's
+rights granted under Section 2(b) shall terminate as of the date such
+litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it
+fails to comply with any of the material terms or conditions of this
+Agreement and does not cure such failure in a reasonable period of
+time after becoming aware of such noncompliance. If all Recipient's
+rights under this Agreement terminate, Recipient agrees to cease use
+and distribution of the Program as soon as reasonably practicable.
+However, Recipient's obligations under this Agreement and any licenses
+granted by Recipient relating to the Program shall continue and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement,
+but in order to avoid inconsistency the Agreement is copyrighted and
+may only be modified in the following manner. The Agreement Steward
+reserves the right to publish new versions (including revisions) of
+this Agreement from time to time. No one other than the Agreement
+Steward has the right to modify this Agreement. The Eclipse Foundation
+is the initial Agreement Steward. The Eclipse Foundation may assign the
+responsibility to serve as the Agreement Steward to a suitable separate
+entity. Each new version of the Agreement will be given a distinguishing
+version number. The Program (including Contributions) may always be
+Distributed subject to the version of the Agreement under which it was
+received. In addition, after a new version of the Agreement is published,
+Contributor may elect to Distribute the Program (including its
+Contributions) under the new version.
+
+Except as expressly stated in Sections 2(a) and 2(b) above, Recipient
+receives no rights or licenses to the intellectual property of any
+Contributor under this Agreement, whether expressly, by implication,
+estoppel or otherwise. All rights in the Program not expressly granted
+under this Agreement are reserved. Nothing in this Agreement is intended
+to be enforceable by any entity that is not a Contributor or Recipient.
+No third-party beneficiary rights are created under this Agreement.
+
+Exhibit A - Form of Secondary Licenses Notice
+
+"This Source Code may also be made available under the following 
+Secondary Licenses when the conditions for such availability set forth 
+in the Eclipse Public License, v. 2.0 are satisfied: {name license(s),
+version(s), and exceptions or additional permissions here}."
+
+  Simply including a copy of this Agreement, including this Exhibit A
+  is not sufficient to license the Source Code under Secondary Licenses.
+
+  If it is not possible or desirable to put the notice in a particular
+  file, then You may include the notice in a location (such as a LICENSE
+  file in a relevant directory) where a recipient would be likely to
+  look for such a notice.
+
+  You may add additional accurate notices of copyright ownership.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0d5a79d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,42 @@
+<!--
+ * Copyright (C) 2020 EGit Committers and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+-->
+
+# EGit Jenkins Pipelines
+
+This repository contains the EGit Jenkins pipeline library.
+
+For general information about Jenkins pipeline shared libraries see the
+[Jenkins documentation](https://jenkins.io/doc/book/pipeline/shared-libraries/).
+
+Jenkins pipelines are written in Groovy; for development in Eclipse it may
+help to install the [Groovy Development Tools](https://marketplace.eclipse.org/content/groovy-development-tools).
+Be aware, though, that GDT patches the JDT Java compiler; a particular version
+of GDT thus works only with a particular version of JDT. If you use an Eclipse
+I-build (nightly development build for the next release), GDT will fail to
+install.
+
+The library is intended to be used for the [Jenkins builds](https://ci.eclipse.org/egit/)
+of the egit/egit and the egit/egit-github repositories.
+
+It provides several kinds of general pipelines that can be configured:
+
+* `verifyBuild` is a simple pipeline that builds and runs the tests for a Gerrit patch set.
+* `productBuild` is intended to be run when a Gerrit patch set is submitted and builds a full distribution (nightly or stable build).
+
+`uiNode` encapsulates the general Jenkins slave setup to run a build including UI tests on a JIRO node.
+
+Directory `src` contains auxiliary Groovy classes encapsulating generally useful operations.
+
+## License
+
+The content of this repository is licensed under the [EPL 2.0](https://www.eclipse.org/legal/epl-2.0).
+
+SPDX-License-Identifier: EPL-2.0
diff --git a/src/org/eclipse/egit/jenkins/Lib.groovy b/src/org/eclipse/egit/jenkins/Lib.groovy
new file mode 100644
index 0000000..ba1cc35
--- /dev/null
+++ b/src/org/eclipse/egit/jenkins/Lib.groovy
@@ -0,0 +1,195 @@
+#!/usr/bin/env groovy
+
+/*******************************************************************************
+ * Copyright (C) 2020 EGit Committers and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.egit.jenkins
+
+/**
+ * EGit build library collecting operations to determine versions and paths depending on versions.
+ */
+class Lib implements Serializable {
+
+	private final def script
+
+	Lib(def script) {
+		this.script = script
+	}
+
+	/**
+	 * Checks that {@code config} contains all the keys of {@code mandatory}. For each missing key,
+	 * writes the corresponding value from {@code mandatory}, which is thus supposed to contain a
+	 * parameter description. Fails the build if not all mandatory keys exists in {@code config}.
+	 *
+	 * @param config
+	 * 		Map to check
+	 * @param mandatory
+	 * 		Map of mandatory keys and their descriptions
+	 */
+	def void configCheck(Map config, Map mandatory) {
+		boolean missing = false
+		for (m in mandatory) {
+			if (!config.containsKey(m.key)) {
+				missing = true
+				script.println('[WARN] Missing parameter ' + m.key + ': ' + m.value)
+			}
+		}
+		if (missing) {
+			script.error('Mandatory parameters missing')
+		}
+	}
+
+	/**
+	 * Reads the &lt;version> tag from the given pom file.
+	 *
+	 * @param pom
+	 * 		path to the pom.xml relative to the current working directory
+	 */
+	def String getOwnVersion(String pom) {
+		return (script.readFile(file: pom, encoding: 'UTF-8') =~ /<version>([^<>]*)<\/version>/)[0][1]
+	}
+
+	/**
+	 * Determines the upstream version.
+	 * <p>
+	 * If we're a -SNAPSHOT version, get the existing snapshot repositories
+	 * and take the highest matching the major.minor number.
+	 * </p>
+	 * <p>
+	 * Otherwise, find the most recent tag matching our own version; i.e.
+	 * if we're 5.3.-something we take the most recent tag v5.3.x.yyyymmddhhmm
+	 * If we're a release, we only consider release tags (suffix "-r")
+	 * </p>
+	 *
+	 * @param upstreamRepoPath
+	 * 		the path at which the upstream is to be found, for instance "orbit" for the Eclipse project "orbit/orbit-recipes"
+	 * @param upstreamProject
+	 * 		the project name (== repository name), for instance "orbit-recipes" for the Eclipse project "orbit/orbit-recipes"
+	 * @param ownVersion
+	 * 		the version of this project
+	 * @return the needed upstream version
+	 */
+	def String getUpstreamVersion(String upstreamRepoPath, String upstreamProject, String ownVersion) {
+		if (ownVersion.endsWith('-SNAPSHOT')) {
+			return getUpstreamSnapshotVersion(upstreamProject, ownVersion)
+		}
+		def tags = getTags("git://git.eclipse.org/gitroot/${upstreamRepoPath}/${upstreamProject}.git").trim().split(/\v+/)
+		def tag = ''
+		int max = -1
+		long maxTime = -1
+		def isRelease = ownVersion.endsWith('-r')
+		for (int i = tags.length - 1; i >= 0; i--) {
+			def t = tags[i].trim()
+			def m = t =~ /.*refs\/tags\/v(\d+\.\d+)\.(\d+)\.(\d+)(-.*)/
+			if (m && ownVersion.startsWith(m[0][1])) {
+				if (isRelease && !t.endsWith('-r')) {
+					continue
+				}
+				int patch = m[0][2] as Integer
+				long date = m[0][3] as Long
+				if (patch > max) {
+					max = patch
+					maxTime = date
+					tag = m[0][1] + '.' + m[0][2] + '.' + m[0][3] + m[0][4]
+				} else if (patch == max && date > maxTime) {
+					maxTime = date
+					tag = m[0][1] + '.' + m[0][2] + '.' + m[0][3] + m[0][4]
+				}
+			}
+		}
+		return tag
+	}
+
+	private def getUpstreamSnapshotVersion(String upstreamProject, String ownVersion) {
+		// Slightly ugly HTML slurping. Is there a better way?
+		// def url = new URL("https://repo.eclipse.org/content/unzip/snapshots.unzip/org/eclipse/" + upstreamProject + "/org.eclipse." + upstreamProject + ".repository/")
+		// def text = url.getText()
+		// The above new URL() is forbidden by Jenkins' script security...
+		def text = script.sh(returnStdout: true, script: "curl -s -f -m 10 -L --max-redirs 8 https://repo.eclipse.org/content/unzip/snapshots.unzip/org/eclipse/${upstreamProject}/org.eclipse.${upstreamProject}.repository/")
+		// TODO: Windows ?
+		def data = text.split(/\v+/)
+		def version = ownVersion
+		int max = -1
+		for (int i = 0; i < data.length; i++) {
+			def m = data[i] =~ /<a href="[^"]*\/(\d+\.\d+)\.(\d+)-SNAPSHOT\/"[^>]*>/
+			if (m && ownVersion.startsWith(m[0][1])) {
+				int patch = m[0][2] as Integer
+				if (patch > max) {
+					max = patch
+					version = m[0][1] + '.' + m[0][2] + '-SNAPSHOT'
+				}
+			}
+		}
+		return version
+	}
+
+	private def String getTags(String url) {
+		def cmd = "git ls-remote --tags --refs ${url}"
+		if (isUnix()) {
+			return script.sh(returnStdout: true, script: cmd)
+		} else {
+			return script.bat(returnStdout: true, script: '@' + cmd)
+		}
+	}
+
+	/**
+	 * Determines the maven command line option giving the upstream repository to use for the maven build.
+	 *
+	 * @param project
+	 * 		name of the upstream project ("jgit" or "egit")
+	 * @param version
+	 * 		upstream version as determined by {@link #getUpstreamVersion(String, String, String)}
+	 *
+	 * @return the complete maven command line option "-Djgit-site=" or "-Degit-site="
+	 */
+	def String getMvnUpstreamRepo(String project, String version) {
+		def repo = "-D${project}-site=https://repo.eclipse.org/content/unzip/"
+		if (version.endsWith('-SNAPSHOT')) {
+			repo += 'snapshots.unzip/org/eclipse/'
+		} else {
+			repo += 'releases.unzip/org/eclipse/'
+		}
+		repo += project
+		def pkg = "org.eclipse.${project}.repository"
+		repo += "/${pkg}/${version}/${pkg}-${version}.zip-unzip/"
+		return repo
+	}
+
+	/**
+	 * Determines the folder to publish to.
+	 *
+	 * @param ownVersion
+	 * 		version of the project being built
+	 * @return The subfolder name to publish the p2 repo to
+	 */
+	def String getPublishFolder(String ownVersion) {
+		if (ownVersion.endsWith('-SNAPSHOT')) {
+			if (script.env.GERRIT_BRANCH.contains('master')) {
+				return 'updates-nightly';
+			} else {
+				// We only ever build the last release or master, so two directories are sufficient
+				return 'updates-stable-nightly'
+			}
+		} else {
+			// Gotta be a release.
+			if (ownVersion.endsWith('-r')) {
+				def m = ownVersion =~ /(\d+\.\d+)\.(\d+).*/
+				def patch = m[0][2] as Integer
+				if (patch == 0) {
+					return 'updates-' + m[0][1]
+				} else {
+					return 'updates-' + m[0][1] + '.' + m[0][2]
+				}
+			} else {
+				return 'staging/v' + ownVersion
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/org/eclipse/egit/jenkins/Tools.groovy b/src/org/eclipse/egit/jenkins/Tools.groovy
new file mode 100644
index 0000000..6f3861e
--- /dev/null
+++ b/src/org/eclipse/egit/jenkins/Tools.groovy
@@ -0,0 +1,226 @@
+#!/usr/bin/env groovy
+
+/*******************************************************************************
+ * Copyright (C) 2020 EGit Committers and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.egit.jenkins
+
+/**
+ * Collection of useful pipeline fragments for use in an EGit build.
+ */
+class Tools implements Serializable {
+
+	private final def script
+
+	Tools(def script) {
+		this.script = script
+	}
+
+	/**
+	 * Constructs a GerritTrigger "gerritProjects" specification to trigger on the given
+	 * {@code repo} and {@code branches}.
+	 *
+	 * @param match
+	 * 		match type for {@code repo}
+	 * @param repo
+	 * 		to trigger on
+	 * @param branches
+	 * 		to trigger on; single entry or a Collection of entries, each entry either
+	 * 		a simple string or a list containing two strings, the first one the match
+	 * 		type and the second one the name or pattern. If empty or {@code null}, no
+	 *      branch filter will be added.
+	 * @return the "gerritProjects" specification
+	 */
+	def Object projectsToBuild(String match, String repo, def branches = null) {
+		def branchSpecs = []
+		if (!branches) {
+			return [
+				[$class: "GerritProject", compareType: match, pattern: repo]
+			]
+		}
+		if (branches instanceof String) {
+			branchSpecs.add([$class: "Branch", compareType: "PLAIN", pattern: b])
+		} else {
+			for (b in branches) {
+				if (b instanceof Collection && b.size() == 2) {
+					branchSpecs.add([$class: "Branch", compareType: b[0], pattern: b[1]])
+				} else {
+					branchSpecs.add([$class: "Branch", compareType: "PLAIN", pattern: b])
+				}
+			}
+		}
+		return [
+			[$class: "GerritProject", compareType: match, pattern: repo, branches: branchSpecs]
+		]
+	}
+
+	/**
+	 * A complete "Checkout" stage for EGit projects, cloning a given {@code project} using
+	 * the given {@code refSpec} and checking out a given {@code branch}.
+	 *
+	 * @param project
+	 * 		to clone; for example "jgit/jgit"
+	 * @param branch
+	 * 		to check out
+	 * @param refSpec
+	 * 		to use
+	 * @param extras
+	 *		map of extra parameters for the checkout() command
+	 */
+	def void cloneAndCheckout(String project, String branch, String refSpec, Map extras = [:]) {
+		def cfg = [
+			$class: 'GitSCM',
+			branches: [[name: branch]],
+			doGenerateSubmoduleConfigurations: false,
+			submoduleCfg: [],
+			userRemoteConfigs: [
+				[url : "git://git.eclipse.org/gitroot/${project}.git", name : 'origin', refspec : refSpec]
+			]
+		]
+		for (extra in extras) {
+			cfg.put(extra.key, extra.value)
+		}
+		script.checkout(cfg)
+	}
+
+	/**
+	 * Copies all content of a {@code sourceDirectory} to a {@code publishDirectory} on projects-storage.eclipse.org
+	 * via ssh/scp. If the {@code publishDirectory} exists already, it is replaced.
+	 *
+	 * @param genie
+	 * 		user name to log in at projects-storage.eclipse.org, typically "genie.egit"
+	 * @param credentials
+	 * 		Jenkins credential name for the ssh key to use
+	 * @param sourceDirectory
+	 * 		path relative to ${WORKSPACE} of the directory to copy
+	 * @param publishDirectory
+	 * 		path on projects-storage.eclipse.org to copy to
+	 * @param extraSource
+	 * 		to also copy to {@code publishDirectory}
+	 */
+	def void publishUpdateSite(String genie, String credentials, String sourceDirectory, String publishDirectory, String extraSource = null) {
+		script.sshagent ([credentials]) {
+			script.sh """
+					ssh ${genie}@projects-storage.eclipse.org rm -rf ${publishDirectory}-tmp
+					ssh ${genie}@projects-storage.eclipse.org mkdir -p ${publishDirectory}-tmp
+					scp -r ${sourceDirectory}/* ${genie}@projects-storage.eclipse.org:${publishDirectory}-tmp
+				"""
+			if (extraSource) {
+				script.sh """
+					scp ${extraSource} ${genie}@projects-storage.eclipse.org:${publishDirectory}-tmp/
+				"""
+			}
+			// Remove former -old directory. There shouldn't be one, but let's be sure.
+			// Ensure the publishDirectory exists before moving it to -old.
+			// Then remane -tmp and remove -old.
+			script.sh """
+					ssh ${genie}@projects-storage.eclipse.org rm -rf ${publishDirectory}-old
+					ssh ${genie}@projects-storage.eclipse.org mkdir -p ${publishDirectory}
+					ssh ${genie}@projects-storage.eclipse.org mv ${publishDirectory} ${publishDirectory}-old
+					ssh ${genie}@projects-storage.eclipse.org mv ${publishDirectory}-tmp ${publishDirectory}
+					ssh ${genie}@projects-storage.eclipse.org rm -rf ${publishDirectory}-old
+				"""
+		}
+	}
+
+	/**
+	 * Standard EGit build reporting steps, including artifact archiving.
+	 *
+	 * @param specificArtifacts
+	 * 		Collection of ${WORKSPACE}-relative ant patterns defining the artifacts to archive;
+	 * 		screenshots and Eclipse log files are added automatically
+	 */
+	def void reporting(Collection specificArtifacts = []) {
+		// don't use ** if the number of directories is known, this is a huge performance problem
+		script.junit '*/target/surefire-reports/*.xml'
+
+		def artifacts = []
+		artifacts.addAll(specificArtifacts)
+		artifacts.addAll([
+			'*/target/screenshots/*',
+			'*/target/work/data/.metadata/*log',
+		])
+		script.archiveArtifacts artifacts.join(',')
+
+		// TODO replace by warnings-next-generation once it is installed
+		script.findbugs pattern: '*/target/*bugsXml.xml', defaultEncoding: 'UTF-8'
+		script.dry defaultEncoding: 'UTF-8'
+	}
+
+	/**
+	 * Sends an e-mail depending on the build outcome.
+	 *
+	 * @param to
+	 * 		E-mail recipients; a whitespace-separated sequence of e-mail addresses
+	 */
+	def void sendMail(String to) {
+		if (script.currentBuild.result == null) {
+			script.currentBuild.result = script.currentBuild.currentResult
+		}
+		script.step([
+			$class: 'Mailer',
+			notifyEveryUnstableBuild: true,
+			recipients: to,
+			sendToIndividuals: true
+		])
+	}
+
+	/**
+	 * Runs mvn with the given arguments, supplying the EGit default arguments automatically.
+	 *
+	 * @param arguments
+	 * 		for mvn
+	 * @param mvnVersion
+	 *		Jenkins tool identifier; defaults to the latest mvn version available
+	 */
+	def void maven(Collection arguments, String mvnVersion = 'apache-maven-latest') {
+		def args = []
+		args.addAll(arguments)
+		// General build setup
+		args.addAll([
+			// suppress progress output
+			'--batch-mode',
+			// show maven errors
+			'--errors',
+			// have a separate maven repo per job
+			"-Dmaven.repo.local=${script.env.WORKSPACE}/.repository",
+			// avoid flaky or not updated mirrors
+			'-Declipse.p2.mirrors=false',
+			// temporary directory for egit tests
+			"-Degit.test.tmpdir=${script.env.WORKSPACE}/tmp/egit.tmp/",
+			// temporary directory for java
+			"-Djava.io.tmpdir=${script.env.WORKSPACE}/tmp/",
+		])
+		// mvn logging setup
+		args.addAll([
+			// make eclipse log to the build log, not just into a file
+			'-Dtest.vmparams=-Declipse.consoleLog=true',
+			// enable timestamps in mvn logging
+			'-Dorg.slf4j.simpleLogger.showDateTime=true',
+			// set timestamp format
+			'-Dorg.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss',
+			// disable download progress output by allowing warning output only
+			'-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn',
+			// disable parallel maven build threadsafe warning by allowing error output only
+			'-Dorg.slf4j.simpleLogger.log.org.apache.maven.lifecycle.internal.builder.BuilderCommon=error'
+		])
+		def command = args.join(' ')
+
+		// get the path from the global Jenkins configuration
+		def mvnHome = script.tool mvnVersion
+
+		// invoke maven
+		if (script.isUnix()) {
+			script.sh "'${mvnHome}/bin/mvn' ${command}"
+		} else {
+			script.bat(/"${mvnHome}\bin\mvn" ${command}/)
+		}
+	}
+}
\ No newline at end of file
diff --git a/vars/egitGithubProductBuild.groovy b/vars/egitGithubProductBuild.groovy
new file mode 100644
index 0000000..f911e00
--- /dev/null
+++ b/vars/egitGithubProductBuild.groovy
@@ -0,0 +1,40 @@
+#!/usr/bin/env groovy
+
+/*******************************************************************************
+ * Copyright (C) 2020 EGit Committers and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+
+/**
+ * EGit-Github product build for a given branch, either determined by the gerrit trigger's
+ * $GERRIT_BRANCH or the {@code defaultBranch}. upstreamVersion set from the job parameters,
+ * where EGit jobs set it when building a specific version.
+ *
+ * @param lib
+ * 		library to use
+ * @param tooling
+ * 		to use
+ * @param cfg
+ * 		configuration
+ * @return
+ */
+def call(def lib, def tooling, Map cfg = [:]) {
+	Map config = [
+		timeOut : 30,
+		repoPath : 'egit/egit-github',
+		// defaultBranch from cfg
+		upstreamRepoPath : 'egit',
+		upstreamRepo : 'egit',
+		upstreamVersion: params.EGIT_VERSION,
+		p2project : 'org.eclipse.mylyn.github-site',
+		p2zip : 'github-updatesite-*.zip',
+		publishRoot : 'egit/github'
+	]
+	productBuild(lib, tooling, config << cfg)
+}
\ No newline at end of file
diff --git a/vars/egitGithubVerifyBuild.groovy b/vars/egitGithubVerifyBuild.groovy
new file mode 100644
index 0000000..408e6c7
--- /dev/null
+++ b/vars/egitGithubVerifyBuild.groovy
@@ -0,0 +1,37 @@
+#!/usr/bin/env groovy
+
+/*******************************************************************************
+ * Copyright (C) 2020 EGit Committers and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+
+/**
+ * EGit-Github Gerrit patchset verify build for a given branch, either determined by the gerrit trigger's
+ * $GERRIT_BRANCH or the {@code defaultBranch}.
+ *
+ * @param lib
+ * 		library to use
+ * @param tooling
+ * 		to use
+ * @param cfg
+ * 		configuration
+ * @return
+ */
+def call(def lib, def tooling, Map cfg = [:]) {
+	Map config = [
+		timeOut : 30,
+		repoPath : 'egit/egit-github',
+		// defaultBranch from cfg
+		upstreamRepoPath : 'egit',
+		upstreamRepo : 'egit',
+		upstreamVersion: params.EGIT_VERSION,
+		p2project : 'org.eclipse.mylyn.github-site',
+	]
+	verifyBuild(lib, tooling, config << cfg)
+}
\ No newline at end of file
diff --git a/vars/egitProductBuild.groovy b/vars/egitProductBuild.groovy
new file mode 100644
index 0000000..c253040
--- /dev/null
+++ b/vars/egitProductBuild.groovy
@@ -0,0 +1,40 @@
+#!/usr/bin/env groovy
+
+/*******************************************************************************
+ * Copyright (C) 2020 EGit Committers and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+
+/**
+ * EGit product build for a given branch, either determined by the gerrit trigger's
+ * $GERRIT_BRANCH or the {@code defaultBranch}.
+ *
+ * @param lib
+ * 		library to use
+ * @param tooling
+ * 		to use
+ * @param cfg
+ * 		configuration
+ * @return
+ */
+def call(def lib, def tooling, Map cfg = [:]) {
+	Map config = [
+		timeOut : 60,
+		repoPath : 'egit/egit',
+		// defaultBranch from cfg
+		upstreamRepoPath : 'jgit',
+		upstreamRepo : 'jgit',
+		// upstreamVersion from cfg or auto-determined
+		p2project : 'org.eclipse.egit.repository',
+		p2zip : 'org.eclipse.egit.repository-*.zip',
+		publishRoot : 'egit',
+		// downstreamJob from cfg
+	]
+	productBuild(lib, tooling, config << cfg)
+}
\ No newline at end of file
diff --git a/vars/egitVerifyBuild.groovy b/vars/egitVerifyBuild.groovy
new file mode 100644
index 0000000..4b05e51
--- /dev/null
+++ b/vars/egitVerifyBuild.groovy
@@ -0,0 +1,37 @@
+#!/usr/bin/env groovy
+
+/*******************************************************************************
+ * Copyright (C) 2020 EGit Committers and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+
+/**
+ * EGit Gerrit patchset product build for a given branch, either determined by the gerrit trigger's
+ * $GERRIT_BRANCH or the {@code defaultBranch}.
+ *
+ * @param lib
+ * 		library to use
+ * @param tooling
+ * 		to use
+ * @param cfg
+ * 		configuration
+ * @return
+ */
+def call(def lib, def tooling, Map cfg = [:]) {
+	Map config = [
+		timeOut : 60,
+		repoPath : 'egit/egit',
+		// defaultBranch from cfg
+		upstreamRepoPath : 'jgit',
+		upstreamRepo : 'jgit',
+		// upstreamVersion from cfg or auto-determined
+		p2project : 'org.eclipse.egit.repository',
+	]
+	verifyBuild(lib, tooling, config << cfg)
+}
\ No newline at end of file
diff --git a/vars/productBuild.groovy b/vars/productBuild.groovy
new file mode 100644
index 0000000..22971f0
--- /dev/null
+++ b/vars/productBuild.groovy
@@ -0,0 +1,111 @@
+#!/usr/bin/env groovy
+
+/*******************************************************************************
+ * Copyright (C) 2020 EGit Committers and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+
+/**
+ * EGit product build for a given branch, either determined by the gerrit trigger's
+ * $GERRIT_BRANCH or the {@code cfg.defaultBranch}.
+ *
+ * @param lib
+ * 		library to use
+ * @param tooling
+ * 		to use
+ * @param cfg
+ * 		configuration
+ * @return
+ */
+def call(def lib, def tooling, Map cfg = [:]) {
+	Map config = [timeOut : 60] << cfg
+	// Check parameters
+	lib.configCheck(config, [
+		timeOut : 'Job timeout in minutes, default 60',
+		repoPath : 'Full path to the repository to build, for instance "egit/egit".',
+		// defaultBranch is optional: branch to build if $GERRIT_BRANCH is not set
+		upstreamRepoPath: 'Path to the upstream repo, for instance the first "jgit" for "jgit/jgit".',
+		upstreamRepo: 'Upstream repository name, for instance the second "jgit" for "jgit/jgit".',
+		// upstreamVersion is optional; auto-determined if not set
+		p2project: 'Project containing the built update site at target/repository.',
+		// p2zip is optional: p2 repo zip to also copy during deployment,
+		publishRoot: 'Folder on p2 publish server under which the repo should be placed. The appropriate subfolder is determined automatically.',
+		// downstreamJob is optional: job to trigger after deployment
+	])
+	uiNode(config.timeOut) {
+		if (!env.GERRIT_BRANCH) {
+			env.GERRIT_BRANCH = config.defaultBranch
+		}
+		try {
+			stage('Checkout') {
+				tooling.cloneAndCheckout(config.repoPath, env.GERRIT_BRANCH, '+refs/heads/*:refs/remotes/origin/*');
+			}
+			def ownVersion = lib.getOwnVersion('pom.xml')
+			def publishFolder = "/${publishRoot}/" + lib.getPublishFolder(ownVersion)
+			def publishDirectory = '/home/data/httpd/download.eclipse.org' + publishFolder
+			def upstreamVersion = config.upstreamVersion
+			if (!upstreamVersion) {
+				upstreamVersion = lib.getUpstreamVersion(config.upstreamRepoPath, config.upstreamRepo, ownVersion)
+			}
+			def commonMvnArguments = [
+				'-Pstatic-checks,other-os,eclipse-sign',
+				lib.getMvnUpstreamRepo(config.upstreamRepo, upstreamVersion),
+				// Needed by tycho-eclipserun for the p2 mirrors URL
+				"-DPUBLISH_FOLDER=${publishFolder}"
+			]
+			stage('Build') {
+				def arguments = [
+					'clean',
+					'install'
+				]
+				arguments.addAll(commonMvnArguments)
+				tooling.maven(arguments)
+			}
+			stage('Deploy') {
+				// Nexus
+				def arguments = [
+					'deploy',
+					'-DskipTests=true',
+					'-Dskip-ui-tests=true'
+				]
+				arguments.addAll(commonMvnArguments)
+				tooling.maven(arguments)
+				// Update site
+				def extraSource = null
+				if (config.p2zip && ownVersion.endsWith('-r')) {
+					// Must be a stable build...
+					extraSource = config.p2project + '/target/' + config.p2zip
+				}
+				tooling.publishUpdateSite(
+						'genie.egit',
+						'projects-storage.eclipse.org-bot-ssh',
+						config.p2project + '/target/repository',
+						publishDirectory,
+						extraSource)
+			}
+			if (config.downstreamJob) {
+				build(
+						job: config.downstreamJob,
+						propagate: false,
+						wait: false,
+						parameters: [
+							[$class: 'StringParameterValue', name: 'EGIT_VERSION', value: ownVersion]
+						])
+			}
+		}
+		finally { // replacement for post actions of Jenkins 1.x
+			stage('Results') {
+				tooling.reporting([
+					config.p2project + '/target/repository/**'
+				])
+			}
+			tooling.sendMail('egit-build@eclipse.org')
+		}
+	}
+}
\ No newline at end of file
diff --git a/vars/uiNode.groovy b/vars/uiNode.groovy
new file mode 100644
index 0000000..fa41a49
--- /dev/null
+++ b/vars/uiNode.groovy
@@ -0,0 +1,43 @@
+#!/usr/bin/env groovy
+
+/*******************************************************************************
+ * Copyright (C) 2020 EGit Committers and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+
+/**
+ * Runs {@code body} with the given {@code timeOut} on a Linux UI node with Xvnc display
+ * and a window manager.
+ *
+ * @param timeOut in minutes for the whole {@code body}
+ * @param body to execute
+ * @return
+ */
+def call(int timeOut, Closure body) {
+	timestamps {
+		// Run only on nodes capable of UI tests ('ui-test' or 'migration')
+		node('migration') {
+			timeout(time: timeOut, unit: 'MINUTES') {
+				// Start the VNC server
+				wrap([$class: 'Xvnc', takeScreenshot: false, useXauthority: true]) {
+					stage('Environment') {
+						// Start the window manager
+						sh 'mutter --replace --sm-disable &'
+						// Create EGit tmp dir
+						sh 'mkdir -p tmp/egit.tmp'
+					}
+					// Use a subdirectory to not overlap with tmp
+					dir('repo') {
+						body()
+					}
+				}
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/vars/verifyBuild.groovy b/vars/verifyBuild.groovy
new file mode 100644
index 0000000..e0ea924
--- /dev/null
+++ b/vars/verifyBuild.groovy
@@ -0,0 +1,74 @@
+#!/usr/bin/env groovy
+
+/*******************************************************************************
+ * Copyright (C) 2020 EGit Committers and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+
+/**
+ * EGit verify build for a given branch.
+ *
+ * @param lib
+ * 		library to use
+ * @param tooling
+ * 		to use
+ * @param cfg
+ * 		configuration
+ * @return
+ */
+def call(def lib, def tooling, Map cfg = [:]) {
+	Map config = [timeOut : 60] << cfg
+	// Check parameters
+	lib.configCheck(config, [
+		timeOut : 'Job timeout in minutes, default 60',
+		repoPath : 'Full path to the repository to build, for instance "egit/egit".',
+		// defaultBranch is optional: branch to build if $GERRIT_BRANCH is not set
+		upstreamRepoPath : 'Path to the upstream repo, for instance the first "jgit" for "jgit/jgit".',
+		upstreamRepo : 'Upstream repository name, for instance the second "jgit" for "jgit/jgit".',
+		// upstreamVersion is optional; auto-determined if not set
+		p2project : 'Project containing the built update site at target/repository.',
+	])
+	uiNode(config.timeOut) {
+		try {
+			if (!env.GERRIT_BRANCH) {
+				env.GERRIT_BRANCH = config.defaultBranch
+			}
+			stage('Checkout') {
+				tooling.cloneAndCheckout(config.repoPath, env.GERRIT_BRANCH, '$GERRIT_REFSPEC', [
+					extensions: [
+						[$class: 'BuildChooserSetting',
+							buildChooser: [$class: 'GerritTriggerBuildChooser']
+						]
+					]
+				])
+			}
+			stage('Build') {
+				def ownVersion = lib.getOwnVersion('pom.xml')
+				def upstreamVersion = config.upstreamVersion
+				if (!upstreamVersion) {
+					upstreamVersion = lib.getUpstreamVersion(config.upstreamRepoPath, config.upstreamRepo, ownVersion)
+				}
+				def arguments = [
+					'clean',
+					'install',
+					'-Pstatic-checks,other-os,eclipse-sign',
+					lib.getMvnUpstreamRepo(config.upstreamRepo, upstreamVersion)
+				]
+				tooling.maven(arguments)
+			}
+		}
+		finally { // replacement for post actions of Jenkins 1.x
+			stage('Results') {
+				tooling.reporting([
+					config.p2project + '/target/repository/**'
+				])
+			}
+		}
+	}
+}
\ No newline at end of file