]> source.dussan.org Git - jgit.git/commitdiff
Add support for pre-push hooks 18/59318/3
authorChristian Halstrick <christian.halstrick@sap.com>
Tue, 30 Jun 2015 14:43:20 +0000 (16:43 +0200)
committerMatthias Sohn <matthias.sohn@sap.com>
Mon, 2 Nov 2015 21:19:49 +0000 (22:19 +0100)
When the file <git-dir>/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

org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java
org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java

index 19f074ea550f3ee05c8c9666b9f4b2cb1d4165e7..ccf1a51f1ba948722cc25d7f7f794098ce4637df 100644 (file)
@@ -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();
index 1494576ab8158e80bd1e0e9c16c2703105156078..6f7a21a73f910b933041cceb25034bbf354b9629 100644 (file)
@@ -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 (file)
index 0000000..2e65828
--- /dev/null
@@ -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 <code>pre-push</code> 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<String> {
+
+       /**
+        * 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<RemoteRefUpdate> 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();
+       }
+}
index 218562254cd4d2e4a8c88fab06ccd8a200611d10..cc7db47df5e4bf31ba859c63277d4f45d7d3d88c 100644 (file)
@@ -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);
        }