From: Christian Halstrick Date: Tue, 30 Jun 2015 14:43:20 +0000 (+0200) Subject: Add support for pre-push hooks X-Git-Tag: v4.2.0.201511101648-m1~19 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=refs%2Fchanges%2F18%2F59318%2F3;p=jgit.git Add support for pre-push hooks When the file /hooks/pre-push exists make sure that is is executing during a push. The pre-push hook runs during git push, after the remote refs have been updated but before any objects have been transferred. Change-Id: Ibbb58ee3227742d1a2f913134ce11e7a135c7f4c --- diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java index 19f074ea55..ccf1a51f1b 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java @@ -47,6 +47,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.util.Properties; @@ -55,7 +56,10 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.hooks.PrePushHook; +import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; @@ -66,6 +70,7 @@ import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.TrackingRefUpdate; import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.util.FS; import org.junit.Test; public class PushCommandTest extends RepositoryTestCase { @@ -107,6 +112,48 @@ public class PushCommandTest extends RepositoryTestCase { db2.resolve(tagRef.getObjectId().getName())); } + @Test + public void testPrePushHook() throws JGitInternalException, IOException, + GitAPIException, URISyntaxException { + + // create other repository + Repository db2 = createWorkRepository(); + + // setup the first repository + final StoredConfig config = db.getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish(db2.getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.update(config); + config.save(); + + File hookOutput = new File(getTemporaryDirectory(), "hookOutput"); + writeHookFile(PrePushHook.NAME, "#!/bin/sh\necho 1:$1, 2:$2, 3:$3 >\"" + + hookOutput.toPath() + "\"\ncat - >>\"" + hookOutput.toPath() + + "\"\nexit 0"); + + Git git1 = new Git(db); + // create some refs via commits and tag + RevCommit commit = git1.commit().setMessage("initial commit").call(); + + RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x"); + git1.push().setRemote("test").setRefSpecs(spec).call(); + assertEquals( + "1:test, 2:file://" + db2.getDirectory().toPath() // + + "/, 3:\n" + "refs/heads/master " + commit.getName() + + " refs/heads/x " + ObjectId.zeroId().name(), + read(hookOutput)); + } + + private File writeHookFile(final String name, final String data) + throws IOException { + File path = new File(db.getWorkTree() + "/.git/hooks/", name); + JGitTestUtil.write(path, data); + FS.DETECTED.setExecute(path, true); + return path; + } + + @Test public void testTrackingUpdate() throws Exception { Repository db2 = createBareRepository(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java index 1494576ab8..6f7a21a73f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java @@ -74,4 +74,15 @@ public class Hooks { PrintStream outputStream) { return new CommitMsgHook(repo, outputStream); } + + /** + * @param repo + * @param outputStream + * The output stream, or {@code null} to use {@code System.out} + * @return The pre-push hook for the given repository. + * @since 4.2 + */ + public static PrePushHook prePush(Repository repo, PrintStream outputStream) { + return new PrePushHook(repo, outputStream); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java new file mode 100644 index 0000000000..2e6582819f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2015 Obeo. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 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 OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.hooks; + +import java.io.IOException; +import java.io.PrintStream; +import java.util.Collection; + +import org.eclipse.jgit.api.errors.AbortedByHookException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.RemoteRefUpdate; + +/** + * The pre-push hook implementation. The pre-push hook runs during + * git push, after the remote refs have been updated but before any objects have + * been transferred. + * + * @since 4.2 + */ +public class PrePushHook extends GitHook { + + /** + * Constant indicating the name of the pre-push hook. + */ + public static final String NAME = "pre-push"; //$NON-NLS-1$ + + private String remoteName; + + private String remoteLocation; + + private String refs; + + /** + * @param repo + * The repository + * @param outputStream + * The output stream the hook must use. {@code null} is allowed, + * in which case the hook will use {@code System.out}. + */ + protected PrePushHook(Repository repo, PrintStream outputStream) { + super(repo, outputStream); + } + + @Override + protected String getStdinArgs() { + return refs; + } + + @Override + public String call() throws IOException, AbortedByHookException { + if (canRun()) { + doRun(); + } + return ""; //$NON-NLS-1$ + } + + /** + * @return {@code true} + */ + private boolean canRun() { + return true; + } + + @Override + public String getHookName() { + return NAME; + } + + /** + * This hook receives two parameters, which is the name and the location of + * the remote repository. + */ + @Override + protected String[] getParameters() { + return new String[] { remoteName, remoteLocation }; + } + + /** + * @param name + */ + public void setRemoteName(String name) { + remoteName = name; + } + + /** + * @param location + */ + public void setRemoteLocation(String location) { + remoteLocation = location; + } + + /** + * @param toRefs + */ + public void setRefs(Collection toRefs) { + StringBuilder b = new StringBuilder(); + boolean first = true; + for (RemoteRefUpdate u : toRefs) { + if (!first) + b.append("\n"); //$NON-NLS-1$ + else + first = false; + b.append(u.getSrcRef()); + b.append(" "); //$NON-NLS-1$ + b.append(u.getNewObjectId().getName()); + b.append(" "); //$NON-NLS-1$ + b.append(u.getRemoteName()); + b.append(" "); //$NON-NLS-1$ + ObjectId ooid = u.getExpectedOldObjectId(); + b.append((ooid == null) ? ObjectId.zeroId().getName() : ooid + .getName()); + } + refs = b.toString(); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java index 218562254c..cc7db47df5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java @@ -53,6 +53,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.PrintStream; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -70,8 +71,11 @@ import java.util.Map; import java.util.Vector; import java.util.concurrent.CopyOnWriteArrayList; +import org.eclipse.jgit.api.errors.AbortedByHookException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.hooks.Hooks; +import org.eclipse.jgit.hooks.PrePushHook; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; @@ -557,8 +561,13 @@ public abstract class Transport { continue; } - if (proto.canHandle(uri, local, remoteName)) - return proto.open(uri, local, remoteName); + if (proto.canHandle(uri, local, remoteName)) { + Transport tn = proto.open(uri, local, remoteName); + tn.prePush = Hooks.prePush(local, tn.hookOutRedirect); + tn.prePush.setRemoteLocation(uri.toString()); + tn.prePush.setRemoteName(remoteName); + return tn; + } } throw new NotSupportedException(MessageFormat.format(JGitText.get().URINotSupported, uri)); @@ -761,6 +770,9 @@ public abstract class Transport { /** Assists with authentication the connection. */ private CredentialsProvider credentialsProvider; + private PrintStream hookOutRedirect; + + private PrePushHook prePush; /** * Create a new transport instance. * @@ -778,6 +790,7 @@ public abstract class Transport { this.uri = uri; this.objectChecker = tc.newObjectChecker(); this.credentialsProvider = CredentialsProvider.getDefault(); + prePush = Hooks.prePush(local, hookOutRedirect); } /** @@ -1196,6 +1209,15 @@ public abstract class Transport { if (toPush.isEmpty()) throw new TransportException(JGitText.get().nothingToPush); } + if (prePush != null) { + try { + prePush.setRefs(toPush); + prePush.call(); + } catch (AbortedByHookException | IOException e) { + throw new TransportException(e.getMessage(), e); + } + } + final PushProcess pushProcess = new PushProcess(this, toPush, out); return pushProcess.execute(monitor); }