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