View Javadoc
1   /*
2    * Copyright (C) 2014 Matthias Sohn <matthias.sohn@sap.com> 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.util;
11  
12  import static java.nio.charset.StandardCharsets.UTF_8;
13  import static org.junit.Assert.assertEquals;
14  import static org.junit.Assert.assertNull;
15  import static org.junit.Assert.fail;
16  
17  import java.io.ByteArrayOutputStream;
18  import java.io.File;
19  import java.io.IOException;
20  import java.io.PrintStream;
21  
22  import org.eclipse.jgit.api.Git;
23  import org.eclipse.jgit.api.errors.AbortedByHookException;
24  import org.eclipse.jgit.hooks.CommitMsgHook;
25  import org.eclipse.jgit.hooks.PostCommitHook;
26  import org.eclipse.jgit.hooks.PreCommitHook;
27  import org.eclipse.jgit.junit.JGitTestUtil;
28  import org.eclipse.jgit.junit.RepositoryTestCase;
29  import org.eclipse.jgit.lib.ConfigConstants;
30  import org.eclipse.jgit.lib.StoredConfig;
31  import org.eclipse.jgit.revwalk.RevCommit;
32  import org.junit.Assume;
33  import org.junit.Test;
34  
35  public class HookTest extends RepositoryTestCase {
36  
37  	@Test
38  	public void testFindHook() throws Exception {
39  		assumeSupportedPlatform();
40  
41  		assertNull("no hook should be installed",
42  				FS.DETECTED.findHook(db, PreCommitHook.NAME));
43  		File hookFile = writeHookFile(PreCommitHook.NAME,
44  				"#!/bin/bash\necho \"test $1 $2\"");
45  		assertEquals("expected to find pre-commit hook", hookFile,
46  				FS.DETECTED.findHook(db, PreCommitHook.NAME));
47  	}
48  
49  	@Test
50  	public void testFindPostCommitHook() throws Exception {
51  		assumeSupportedPlatform();
52  
53  		assertNull("no hook should be installed",
54  				FS.DETECTED.findHook(db, PostCommitHook.NAME));
55  		File hookFile = writeHookFile(PostCommitHook.NAME,
56  				"#!/bin/bash\necho \"test $1 $2\"");
57  		assertEquals("expected to find post-commit hook", hookFile,
58  				FS.DETECTED.findHook(db, PostCommitHook.NAME));
59  	}
60  
61  	@Test
62  	public void testFailedCommitMsgHookBlocksCommit() throws Exception {
63  		assumeSupportedPlatform();
64  
65  		writeHookFile(CommitMsgHook.NAME,
66  				"#!/bin/sh\necho \"test\"\n\necho 1>&2 \"stderr\"\nexit 1");
67  		Git git = Git.wrap(db);
68  		String path = "a.txt";
69  		writeTrashFile(path, "content");
70  		git.add().addFilepattern(path).call();
71  		ByteArrayOutputStream out = new ByteArrayOutputStream();
72  		try {
73  			git.commit().setMessage("commit")
74  					.setHookOutputStream(new PrintStream(out)).call();
75  			fail("expected commit-msg hook to abort commit");
76  		} catch (AbortedByHookException e) {
77  			assertEquals("unexpected error message from commit-msg hook",
78  					"Rejected by \"commit-msg\" hook.\nstderr\n",
79  					e.getMessage());
80  			assertEquals("unexpected output from commit-msg hook", "test\n",
81  					out.toString(UTF_8));
82  		}
83  	}
84  
85  	@Test
86  	public void testCommitMsgHookReceivesCorrectParameter() throws Exception {
87  		assumeSupportedPlatform();
88  
89  		writeHookFile(CommitMsgHook.NAME,
90  				"#!/bin/sh\necho $1\n\necho 1>&2 \"stderr\"\nexit 0");
91  		Git git = Git.wrap(db);
92  		String path = "a.txt";
93  		writeTrashFile(path, "content");
94  		git.add().addFilepattern(path).call();
95  		ByteArrayOutputStream out = new ByteArrayOutputStream();
96  		git.commit().setMessage("commit")
97  				.setHookOutputStream(new PrintStream(out)).call();
98  		assertEquals(".git/COMMIT_EDITMSG\n",
99  				out.toString(UTF_8));
100 	}
101 
102 	@Test
103 	public void testCommitMsgHookCanModifyCommitMessage() throws Exception {
104 		assumeSupportedPlatform();
105 
106 		writeHookFile(CommitMsgHook.NAME,
107 				"#!/bin/sh\necho \"new message\" > $1\nexit 0");
108 		Git git = Git.wrap(db);
109 		String path = "a.txt";
110 		writeTrashFile(path, "content");
111 		git.add().addFilepattern(path).call();
112 		ByteArrayOutputStream out = new ByteArrayOutputStream();
113 		RevCommit revCommit = git.commit().setMessage("commit")
114 				.setHookOutputStream(new PrintStream(out)).call();
115 		assertEquals("new message\n", revCommit.getFullMessage());
116 	}
117 
118 	@Test
119 	public void testPostCommitRunHook() throws Exception {
120 		assumeSupportedPlatform();
121 
122 		writeHookFile(PostCommitHook.NAME,
123 				"#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\necho 1>&2 \"stderr\"");
124 		ByteArrayOutputStream out = new ByteArrayOutputStream();
125 		ByteArrayOutputStream err = new ByteArrayOutputStream();
126 		ProcessResult res = FS.DETECTED.runHookIfPresent(db,
127 				PostCommitHook.NAME,
128 				new String[] {
129 				"arg1", "arg2" },
130 				new PrintStream(out), new PrintStream(err), "stdin");
131 
132 		assertEquals("unexpected hook output", "test arg1 arg2\nstdin\n",
133 				out.toString(UTF_8));
134 		assertEquals("unexpected output on stderr stream", "stderr\n",
135 				err.toString(UTF_8));
136 		assertEquals("unexpected exit code", 0, res.getExitCode());
137 		assertEquals("unexpected process status", ProcessResult.Status.OK,
138 				res.getStatus());
139 	}
140 
141 	@Test
142 	public void testAllCommitHooks() throws Exception {
143 		assumeSupportedPlatform();
144 
145 		writeHookFile(PreCommitHook.NAME,
146 				"#!/bin/sh\necho \"test pre-commit\"\n\necho 1>&2 \"stderr pre-commit\"\nexit 0");
147 		writeHookFile(CommitMsgHook.NAME,
148 				"#!/bin/sh\necho \"test commit-msg $1\"\n\necho 1>&2 \"stderr commit-msg\"\nexit 0");
149 		writeHookFile(PostCommitHook.NAME,
150 				"#!/bin/sh\necho \"test post-commit\"\necho 1>&2 \"stderr post-commit\"\nexit 0");
151 		Git git = Git.wrap(db);
152 		String path = "a.txt";
153 		writeTrashFile(path, "content");
154 		git.add().addFilepattern(path).call();
155 		ByteArrayOutputStream out = new ByteArrayOutputStream();
156 		try {
157 			git.commit().setMessage("commit")
158 					.setHookOutputStream(new PrintStream(out)).call();
159 		} catch (AbortedByHookException e) {
160 			fail("unexpected hook failure");
161 		}
162 		assertEquals("unexpected hook output",
163 				"test pre-commit\ntest commit-msg .git/COMMIT_EDITMSG\ntest post-commit\n",
164 				out.toString(UTF_8));
165 	}
166 
167 	@Test
168 	public void testRunHook() throws Exception {
169 		assumeSupportedPlatform();
170 
171 		writeHookFile(PreCommitHook.NAME,
172 				"#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\n"
173 						+ "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\"");
174 		ByteArrayOutputStream out = new ByteArrayOutputStream();
175 		ByteArrayOutputStream err = new ByteArrayOutputStream();
176 		ProcessResult res = FS.DETECTED.runHookIfPresent(db,
177 				PreCommitHook.NAME,
178 				new String[] {
179 				"arg1", "arg2" },
180 				new PrintStream(out), new PrintStream(err), "stdin");
181 
182 		assertEquals("unexpected hook output",
183 				"test arg1 arg2\nstdin\n" + db.getDirectory().getAbsolutePath()
184 						+ '\n' + db.getWorkTree().getAbsolutePath() + '\n',
185 				out.toString(UTF_8));
186 		assertEquals("unexpected output on stderr stream", "stderr\n",
187 				err.toString(UTF_8));
188 		assertEquals("unexpected exit code", 0, res.getExitCode());
189 		assertEquals("unexpected process status", ProcessResult.Status.OK,
190 				res.getStatus());
191 	}
192 
193 	@Test
194 	public void testRunHookHooksPathRelative() throws Exception {
195 		assumeSupportedPlatform();
196 
197 		writeHookFile(PreCommitHook.NAME,
198 				"#!/bin/sh\necho \"Wrong hook $1 $2\"\nread INPUT\necho $INPUT\n"
199 						+ "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\"");
200 		writeHookFile("../../" + PreCommitHook.NAME,
201 				"#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\n"
202 						+ "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\"");
203 		StoredConfig cfg = db.getConfig();
204 		cfg.load();
205 		cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
206 				ConfigConstants.CONFIG_KEY_HOOKS_PATH, ".");
207 		cfg.save();
208 		try (ByteArrayOutputStream out = new ByteArrayOutputStream();
209 				ByteArrayOutputStream err = new ByteArrayOutputStream()) {
210 			ProcessResult res = FS.DETECTED.runHookIfPresent(db,
211 					PreCommitHook.NAME, new String[] { "arg1", "arg2" },
212 					new PrintStream(out), new PrintStream(err), "stdin");
213 
214 			assertEquals("unexpected hook output",
215 					"test arg1 arg2\nstdin\n"
216 							+ db.getDirectory().getAbsolutePath() + '\n'
217 							+ db.getWorkTree().getAbsolutePath() + '\n',
218 					out.toString(UTF_8));
219 			assertEquals("unexpected output on stderr stream", "stderr\n",
220 					err.toString(UTF_8));
221 			assertEquals("unexpected exit code", 0, res.getExitCode());
222 			assertEquals("unexpected process status", ProcessResult.Status.OK,
223 					res.getStatus());
224 		}
225 	}
226 
227 	@Test
228 	public void testRunHookHooksPathAbsolute() throws Exception {
229 		assumeSupportedPlatform();
230 
231 		writeHookFile(PreCommitHook.NAME,
232 				"#!/bin/sh\necho \"Wrong hook $1 $2\"\nread INPUT\necho $INPUT\n"
233 						+ "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\"");
234 		writeHookFile("../../" + PreCommitHook.NAME,
235 				"#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\n"
236 						+ "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\"");
237 		StoredConfig cfg = db.getConfig();
238 		cfg.load();
239 		cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
240 				ConfigConstants.CONFIG_KEY_HOOKS_PATH,
241 				db.getWorkTree().getAbsolutePath());
242 		cfg.save();
243 		try (ByteArrayOutputStream out = new ByteArrayOutputStream();
244 				ByteArrayOutputStream err = new ByteArrayOutputStream()) {
245 			ProcessResult res = FS.DETECTED.runHookIfPresent(db,
246 					PreCommitHook.NAME, new String[] { "arg1", "arg2" },
247 					new PrintStream(out), new PrintStream(err), "stdin");
248 
249 			assertEquals("unexpected hook output",
250 					"test arg1 arg2\nstdin\n"
251 							+ db.getDirectory().getAbsolutePath() + '\n'
252 							+ db.getWorkTree().getAbsolutePath() + '\n',
253 					out.toString(UTF_8));
254 			assertEquals("unexpected output on stderr stream", "stderr\n",
255 					err.toString(UTF_8));
256 			assertEquals("unexpected exit code", 0, res.getExitCode());
257 			assertEquals("unexpected process status", ProcessResult.Status.OK,
258 					res.getStatus());
259 		}
260 	}
261 
262 	@Test
263 	public void testHookPathWithBlank() throws Exception {
264 		assumeSupportedPlatform();
265 
266 		File file = writeHookFile("../../a directory/" + PreCommitHook.NAME,
267 				"#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\n"
268 						+ "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\"");
269 		StoredConfig cfg = db.getConfig();
270 		cfg.load();
271 		cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
272 				ConfigConstants.CONFIG_KEY_HOOKS_PATH,
273 				file.getParentFile().getAbsolutePath());
274 		cfg.save();
275 		try (ByteArrayOutputStream out = new ByteArrayOutputStream();
276 				ByteArrayOutputStream err = new ByteArrayOutputStream()) {
277 			ProcessResult res = FS.DETECTED.runHookIfPresent(db,
278 					PreCommitHook.NAME, new String[] { "arg1", "arg2" },
279 					new PrintStream(out), new PrintStream(err), "stdin");
280 
281 			assertEquals("unexpected hook output",
282 					"test arg1 arg2\nstdin\n"
283 							+ db.getDirectory().getAbsolutePath() + '\n'
284 							+ db.getWorkTree().getAbsolutePath() + '\n',
285 					out.toString(UTF_8));
286 			assertEquals("unexpected output on stderr stream", "stderr\n",
287 					err.toString(UTF_8));
288 			assertEquals("unexpected exit code", 0, res.getExitCode());
289 			assertEquals("unexpected process status", ProcessResult.Status.OK,
290 					res.getStatus());
291 		}
292 	}
293 
294 	@Test
295 	public void testFailedPreCommitHookBlockCommit() throws Exception {
296 		assumeSupportedPlatform();
297 
298 		writeHookFile(PreCommitHook.NAME,
299 				"#!/bin/sh\necho \"test\"\n\necho 1>&2 \"stderr\"\nexit 1");
300 		Git git = Git.wrap(db);
301 		String path = "a.txt";
302 		writeTrashFile(path, "content");
303 		git.add().addFilepattern(path).call();
304 		ByteArrayOutputStream out = new ByteArrayOutputStream();
305 		try {
306 			git.commit().setMessage("commit")
307 					.setHookOutputStream(new PrintStream(out)).call();
308 			fail("expected pre-commit hook to abort commit");
309 		} catch (AbortedByHookException e) {
310 			assertEquals("unexpected error message from pre-commit hook",
311 					"Rejected by \"pre-commit\" hook.\nstderr\n",
312 					e.getMessage());
313 			assertEquals("unexpected output from pre-commit hook", "test\n",
314 					out.toString(UTF_8));
315 		}
316 	}
317 
318 	private File writeHookFile(String name, String data)
319 			throws IOException {
320 		File path = new File(db.getWorkTree() + "/.git/hooks/", name);
321 		JGitTestUtil.write(path, data);
322 		FS.DETECTED.setExecute(path, true);
323 		return path;
324 	}
325 
326 	private void assumeSupportedPlatform() {
327 		Assume.assumeTrue(FS.DETECTED instanceof FS_POSIX
328 				|| FS.DETECTED instanceof FS_Win32_Cygwin);
329 	}
330 }