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