1 /* 2 * Copyright (C) 2015 Obeo. and others 3 * 4 * This program and the accompanying materials are made available under the 5 * terms of the Eclipse Distribution License v. 1.0 which is available at 6 * https://www.eclipse.org/org/documents/edl-v10.php. 7 * 8 * SPDX-License-Identifier: BSD-3-Clause 9 */ 10 package org.eclipse.jgit.hooks; 11 12 import java.io.ByteArrayOutputStream; 13 import java.io.IOException; 14 import java.io.OutputStream; 15 import java.util.concurrent.Callable; 16 17 import org.eclipse.jgit.api.errors.AbortedByHookException; 18 import org.eclipse.jgit.lib.Repository; 19 import org.eclipse.jgit.util.FS; 20 import org.eclipse.jgit.util.ProcessResult; 21 import org.eclipse.jgit.util.SystemReader; 22 import org.eclipse.jgit.util.io.TeeOutputStream; 23 24 /** 25 * Git can fire off custom scripts when certain important actions occur. These 26 * custom scripts are called "hooks". There are two groups of hooks: client-side 27 * (that run on local operations such as committing and merging), and 28 * server-side (that run on network operations such as receiving pushed 29 * commits). This is the abstract super-class of the different hook 30 * implementations in JGit. 31 * 32 * @param <T> 33 * the return type which is expected from {@link #call()} 34 * @see <a href="http://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks">Git 35 * Hooks on the git-scm official site</a> 36 * @since 5.11 37 */ 38 public abstract class GitHook<T> implements Callable<T> { 39 40 private final Repository repo; 41 42 /** 43 * The output stream to be used by the hook. 44 */ 45 private final OutputStream outputStream; 46 47 /** 48 * The error stream to be used by the hook. 49 */ 50 private final OutputStream errorStream; 51 52 /** 53 * Constructor for GitHook. 54 * <p> 55 * This constructor will use stderr for the error stream. 56 * </p> 57 * 58 * @param repo 59 * a {@link org.eclipse.jgit.lib.Repository} object. 60 * @param outputStream 61 * The output stream the hook must use. {@code null} is allowed, 62 * in which case the hook will use {@code System.out}. 63 */ 64 protected GitHook(Repository repo, OutputStream outputStream) { 65 this(repo, outputStream, null); 66 } 67 68 /** 69 * Constructor for GitHook 70 * 71 * @param repo 72 * a {@link org.eclipse.jgit.lib.Repository} object. 73 * @param outputStream 74 * The output stream the hook must use. {@code null} is allowed, 75 * in which case the hook will use {@code System.out}. 76 * @param errorStream 77 * The error stream the hook must use. {@code null} is allowed, 78 * in which case the hook will use {@code System.err}. 79 */ 80 protected GitHook(Repository repo, OutputStream outputStream, 81 OutputStream errorStream) { 82 this.repo = repo; 83 this.outputStream = outputStream; 84 this.errorStream = errorStream; 85 } 86 87 /** 88 * {@inheritDoc} 89 * <p> 90 * Run the hook. 91 */ 92 @Override 93 public abstract T call() throws IOException, AbortedByHookException; 94 95 /** 96 * Get name of the hook 97 * 98 * @return The name of the hook, which must not be {@code null}. 99 */ 100 public abstract String getHookName(); 101 102 /** 103 * Get the repository 104 * 105 * @return The repository. 106 */ 107 protected Repository getRepository() { 108 return repo; 109 } 110 111 /** 112 * Override this method when needed to provide relevant parameters to the 113 * underlying hook script. The default implementation returns an empty 114 * array. 115 * 116 * @return The parameters the hook receives. 117 */ 118 protected String[] getParameters() { 119 return new String[0]; 120 } 121 122 /** 123 * Override to provide relevant arguments via stdin to the underlying hook 124 * script. The default implementation returns {@code null}. 125 * 126 * @return The parameters the hook receives. 127 */ 128 protected String getStdinArgs() { 129 return null; 130 } 131 132 /** 133 * Get output stream 134 * 135 * @return The output stream the hook must use. Never {@code null}, 136 * {@code System.out} is returned by default. 137 */ 138 protected OutputStream getOutputStream() { 139 return outputStream == null ? System.out : outputStream; 140 } 141 142 /** 143 * Get error stream 144 * 145 * @return The error stream the hook must use. Never {@code null}, 146 * {@code System.err} is returned by default. 147 */ 148 protected OutputStream getErrorStream() { 149 return errorStream == null ? System.err : errorStream; 150 } 151 152 /** 153 * Runs the hook, without performing any validity checks. 154 * 155 * @throws org.eclipse.jgit.api.errors.AbortedByHookException 156 * If the underlying hook script exited with non-zero. 157 * @throws IOException 158 * if an IO error occurred 159 */ 160 protected void doRun() throws AbortedByHookException, IOException { 161 final ByteArrayOutputStream errorByteArray = new ByteArrayOutputStream(); 162 final TeeOutputStream stderrStream = new TeeOutputStream(errorByteArray, 163 getErrorStream()); 164 Repository repository = getRepository(); 165 FS fs = repository.getFS(); 166 if (fs == null) { 167 fs = FS.DETECTED; 168 } 169 ProcessResult result = fs.runHookIfPresent(repository, getHookName(), 170 getParameters(), getOutputStream(), stderrStream, 171 getStdinArgs()); 172 if (result.isExecutedWithError()) { 173 handleError(new String(errorByteArray.toByteArray(), 174 SystemReader.getInstance().getDefaultCharset().name()), 175 result); 176 } 177 } 178 179 /** 180 * Process that the hook exited with an error. This default implementation 181 * throws an {@link AbortedByHookException }. Hooks which need a different 182 * behavior can overwrite this method. 183 * 184 * @param message 185 * error message 186 * @param result 187 * The process result of the hook 188 * @throws AbortedByHookException 189 * When the hook should be aborted 190 * @since 5.11 191 */ 192 protected void handleError(String message, 193 final ProcessResult result) 194 throws AbortedByHookException { 195 throw new AbortedByHookException(message, getHookName(), 196 result.getExitCode()); 197 } 198 199 /** 200 * Check whether a 'native' (i.e. script) hook is installed in the 201 * repository. 202 * 203 * @return whether a native hook script is installed in the repository. 204 * @since 4.11 205 */ 206 public boolean isNativeHookPresent() { 207 FS fs = getRepository().getFS(); 208 if (fs == null) { 209 fs = FS.DETECTED; 210 } 211 return fs.findHook(getRepository(), getHookName()) != null; 212 } 213 214 }