1 /* 2 * Copyright (C) 2015 Obeo. 3 * and other copyright owners as documented in the project's IP log. 4 * 5 * This program and the accompanying materials are made available 6 * under the terms of the Eclipse Distribution License v1.0 which 7 * accompanies this distribution, is reproduced below, and is 8 * available at http://www.eclipse.org/org/documents/edl-v10.php 9 * 10 * All rights reserved. 11 * 12 * Redistribution and use in source and binary forms, with or 13 * without modification, are permitted provided that the following 14 * conditions are met: 15 * 16 * - Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 19 * - Redistributions in binary form must reproduce the above 20 * copyright notice, this list of conditions and the following 21 * disclaimer in the documentation and/or other materials provided 22 * with the distribution. 23 * 24 * - Neither the name of the Eclipse Foundation, Inc. nor the 25 * names of its contributors may be used to endorse or promote 26 * products derived from this software without specific prior 27 * written permission. 28 * 29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 42 */ 43 package org.eclipse.jgit.hooks; 44 45 import static java.nio.charset.StandardCharsets.UTF_8; 46 47 import java.io.ByteArrayOutputStream; 48 import java.io.IOException; 49 import java.io.PrintStream; 50 import java.io.UnsupportedEncodingException; 51 import java.util.concurrent.Callable; 52 53 import org.bouncycastle.util.io.TeeOutputStream; 54 import org.eclipse.jgit.api.errors.AbortedByHookException; 55 import org.eclipse.jgit.lib.Repository; 56 import org.eclipse.jgit.util.FS; 57 import org.eclipse.jgit.util.ProcessResult; 58 59 /** 60 * Git can fire off custom scripts when certain important actions occur. These 61 * custom scripts are called "hooks". There are two groups of hooks: client-side 62 * (that run on local operations such as committing and merging), and 63 * server-side (that run on network operations such as receiving pushed 64 * commits). This is the abstract super-class of the different hook 65 * implementations in JGit. 66 * 67 * @param <T> 68 * the return type which is expected from {@link #call()} 69 * @see <a href="http://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks">Git 70 * Hooks on the git-scm official site</a> 71 * @since 4.0 72 */ 73 abstract class GitHook<T> implements Callable<T> { 74 75 private final Repository repo; 76 77 /** 78 * The output stream to be used by the hook. 79 */ 80 protected final PrintStream outputStream; 81 82 /** 83 * The error stream to be used by the hook. 84 */ 85 protected final PrintStream errorStream; 86 87 /** 88 * Constructor for GitHook. 89 * <p> 90 * This constructor will use stderr for the error stream. 91 * </p> 92 * 93 * @param repo 94 * a {@link org.eclipse.jgit.lib.Repository} object. 95 * @param outputStream 96 * The output stream the hook must use. {@code null} is allowed, 97 * in which case the hook will use {@code System.out}. 98 */ 99 protected GitHook(Repository repo, PrintStream outputStream) { 100 this(repo, outputStream, null); 101 } 102 103 /** 104 * Constructor for GitHook 105 * 106 * @param repo 107 * a {@link org.eclipse.jgit.lib.Repository} object. 108 * @param outputStream 109 * The output stream the hook must use. {@code null} is allowed, 110 * in which case the hook will use {@code System.out}. 111 * @param errorStream 112 * The error stream the hook must use. {@code null} is allowed, 113 * in which case the hook will use {@code System.err}. 114 */ 115 protected GitHook(Repository repo, PrintStream outputStream, 116 PrintStream errorStream) { 117 this.repo = repo; 118 this.outputStream = outputStream; 119 this.errorStream = errorStream; 120 } 121 122 /** 123 * {@inheritDoc} 124 * <p> 125 * Run the hook. 126 */ 127 @Override 128 public abstract T call() throws IOException, AbortedByHookException; 129 130 /** 131 * Get name of the hook 132 * 133 * @return The name of the hook, which must not be {@code null}. 134 */ 135 public abstract String getHookName(); 136 137 /** 138 * Get the repository 139 * 140 * @return The repository. 141 */ 142 protected Repository getRepository() { 143 return repo; 144 } 145 146 /** 147 * Override this method when needed to provide relevant parameters to the 148 * underlying hook script. The default implementation returns an empty 149 * array. 150 * 151 * @return The parameters the hook receives. 152 */ 153 protected String[] getParameters() { 154 return new String[0]; 155 } 156 157 /** 158 * Override to provide relevant arguments via stdin to the underlying hook 159 * script. The default implementation returns {@code null}. 160 * 161 * @return The parameters the hook receives. 162 */ 163 protected String getStdinArgs() { 164 return null; 165 } 166 167 /** 168 * Get output stream 169 * 170 * @return The output stream the hook must use. Never {@code null}, 171 * {@code System.out} is returned by default. 172 */ 173 protected PrintStream getOutputStream() { 174 return outputStream == null ? System.out : outputStream; 175 } 176 177 /** 178 * Get error stream 179 * 180 * @return The error stream the hook must use. Never {@code null}, 181 * {@code System.err} is returned by default. 182 */ 183 protected PrintStream getErrorStream() { 184 return errorStream == null ? System.err : errorStream; 185 } 186 187 /** 188 * Runs the hook, without performing any validity checks. 189 * 190 * @throws org.eclipse.jgit.api.errors.AbortedByHookException 191 * If the underlying hook script exited with non-zero. 192 */ 193 protected void doRun() throws AbortedByHookException { 194 final ByteArrayOutputStream errorByteArray = new ByteArrayOutputStream(); 195 final TeeOutputStream stderrStream = new TeeOutputStream(errorByteArray, 196 getErrorStream()); 197 PrintStream hookErrRedirect = null; 198 try { 199 hookErrRedirect = new PrintStream(stderrStream, false, 200 UTF_8.name()); 201 } catch (UnsupportedEncodingException e) { 202 // UTF-8 is guaranteed to be available 203 } 204 Repository repository = getRepository(); 205 FS fs = repository.getFS(); 206 if (fs == null) { 207 fs = FS.DETECTED; 208 } 209 ProcessResult result = fs.runHookIfPresent(repository, getHookName(), 210 getParameters(), getOutputStream(), hookErrRedirect, 211 getStdinArgs()); 212 if (result.isExecutedWithError()) { 213 throw new AbortedByHookException( 214 new String(errorByteArray.toByteArray(), UTF_8), 215 getHookName(), result.getExitCode()); 216 } 217 } 218 219 /** 220 * Check whether a 'native' (i.e. script) hook is installed in the 221 * repository. 222 * 223 * @return whether a native hook script is installed in the repository. 224 * @since 4.11 225 */ 226 public boolean isNativeHookPresent() { 227 FS fs = getRepository().getFS(); 228 if (fs == null) { 229 fs = FS.DETECTED; 230 } 231 return fs.findHook(getRepository(), getHookName()) != null; 232 } 233 234 }