View Javadoc
1   /*
2    * Copyright (C) 2014 Matthias Sohn <matthias.sohn@sap.com>
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.util;
44  
45  import static org.junit.Assert.assertEquals;
46  import static org.junit.Assert.assertNull;
47  import static org.junit.Assert.fail;
48  
49  import java.io.ByteArrayOutputStream;
50  import java.io.File;
51  import java.io.IOException;
52  import java.io.PrintStream;
53  
54  import org.eclipse.jgit.api.Git;
55  import org.eclipse.jgit.api.errors.AbortedByHookException;
56  import org.eclipse.jgit.hooks.CommitMsgHook;
57  import org.eclipse.jgit.hooks.PostCommitHook;
58  import org.eclipse.jgit.hooks.PreCommitHook;
59  import org.eclipse.jgit.junit.JGitTestUtil;
60  import org.eclipse.jgit.junit.RepositoryTestCase;
61  import org.eclipse.jgit.lib.ConfigConstants;
62  import org.eclipse.jgit.lib.StoredConfig;
63  import org.eclipse.jgit.revwalk.RevCommit;
64  import org.junit.Assume;
65  import org.junit.Test;
66  
67  public class HookTest extends RepositoryTestCase {
68  
69  	@Test
70  	public void testFindHook() throws Exception {
71  		assumeSupportedPlatform();
72  
73  		assertNull("no hook should be installed",
74  				FS.DETECTED.findHook(db, PreCommitHook.NAME));
75  		File hookFile = writeHookFile(PreCommitHook.NAME,
76  				"#!/bin/bash\necho \"test $1 $2\"");
77  		assertEquals("expected to find pre-commit hook", hookFile,
78  				FS.DETECTED.findHook(db, PreCommitHook.NAME));
79  	}
80  
81  	@Test
82  	public void testFindPostCommitHook() throws Exception {
83  		assumeSupportedPlatform();
84  
85  		assertNull("no hook should be installed",
86  				FS.DETECTED.findHook(db, PostCommitHook.NAME));
87  		File hookFile = writeHookFile(PostCommitHook.NAME,
88  				"#!/bin/bash\necho \"test $1 $2\"");
89  		assertEquals("expected to find post-commit hook", hookFile,
90  				FS.DETECTED.findHook(db, PostCommitHook.NAME));
91  	}
92  
93  	@Test
94  	public void testFailedCommitMsgHookBlocksCommit() throws Exception {
95  		assumeSupportedPlatform();
96  
97  		writeHookFile(CommitMsgHook.NAME,
98  				"#!/bin/sh\necho \"test\"\n\necho 1>&2 \"stderr\"\nexit 1");
99  		Git git = Git.wrap(db);
100 		String path = "a.txt";
101 		writeTrashFile(path, "content");
102 		git.add().addFilepattern(path).call();
103 		ByteArrayOutputStream out = new ByteArrayOutputStream();
104 		try {
105 			git.commit().setMessage("commit")
106 					.setHookOutputStream(new PrintStream(out)).call();
107 			fail("expected commit-msg hook to abort commit");
108 		} catch (AbortedByHookException e) {
109 			assertEquals("unexpected error message from commit-msg hook",
110 					"Rejected by \"commit-msg\" hook.\nstderr\n",
111 					e.getMessage());
112 			assertEquals("unexpected output from commit-msg hook", "test\n",
113 					out.toString());
114 		}
115 	}
116 
117 	@Test
118 	public void testCommitMsgHookReceivesCorrectParameter() throws Exception {
119 		assumeSupportedPlatform();
120 
121 		writeHookFile(CommitMsgHook.NAME,
122 				"#!/bin/sh\necho $1\n\necho 1>&2 \"stderr\"\nexit 0");
123 		Git git = Git.wrap(db);
124 		String path = "a.txt";
125 		writeTrashFile(path, "content");
126 		git.add().addFilepattern(path).call();
127 		ByteArrayOutputStream out = new ByteArrayOutputStream();
128 		git.commit().setMessage("commit")
129 				.setHookOutputStream(new PrintStream(out)).call();
130 		assertEquals(".git/COMMIT_EDITMSG\n",
131 				out.toString("UTF-8"));
132 	}
133 
134 	@Test
135 	public void testCommitMsgHookCanModifyCommitMessage() throws Exception {
136 		assumeSupportedPlatform();
137 
138 		writeHookFile(CommitMsgHook.NAME,
139 				"#!/bin/sh\necho \"new message\" > $1\nexit 0");
140 		Git git = Git.wrap(db);
141 		String path = "a.txt";
142 		writeTrashFile(path, "content");
143 		git.add().addFilepattern(path).call();
144 		ByteArrayOutputStream out = new ByteArrayOutputStream();
145 		RevCommit revCommit = git.commit().setMessage("commit")
146 				.setHookOutputStream(new PrintStream(out)).call();
147 		assertEquals("new message\n", revCommit.getFullMessage());
148 	}
149 
150 	@Test
151 	public void testPostCommitRunHook() throws Exception {
152 		assumeSupportedPlatform();
153 
154 		writeHookFile(PostCommitHook.NAME,
155 				"#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\necho 1>&2 \"stderr\"");
156 		ByteArrayOutputStream out = new ByteArrayOutputStream();
157 		ByteArrayOutputStream err = new ByteArrayOutputStream();
158 		ProcessResult res = FS.DETECTED.runHookIfPresent(db,
159 				PostCommitHook.NAME,
160 				new String[] {
161 				"arg1", "arg2" },
162 				new PrintStream(out), new PrintStream(err), "stdin");
163 
164 		assertEquals("unexpected hook output", "test arg1 arg2\nstdin\n",
165 				out.toString("UTF-8"));
166 		assertEquals("unexpected output on stderr stream", "stderr\n",
167 				err.toString("UTF-8"));
168 		assertEquals("unexpected exit code", 0, res.getExitCode());
169 		assertEquals("unexpected process status", ProcessResult.Status.OK,
170 				res.getStatus());
171 	}
172 
173 	@Test
174 	public void testAllCommitHooks() throws Exception {
175 		assumeSupportedPlatform();
176 
177 		writeHookFile(PreCommitHook.NAME,
178 				"#!/bin/sh\necho \"test pre-commit\"\n\necho 1>&2 \"stderr pre-commit\"\nexit 0");
179 		writeHookFile(CommitMsgHook.NAME,
180 				"#!/bin/sh\necho \"test commit-msg $1\"\n\necho 1>&2 \"stderr commit-msg\"\nexit 0");
181 		writeHookFile(PostCommitHook.NAME,
182 				"#!/bin/sh\necho \"test post-commit\"\necho 1>&2 \"stderr post-commit\"\nexit 0");
183 		Git git = Git.wrap(db);
184 		String path = "a.txt";
185 		writeTrashFile(path, "content");
186 		git.add().addFilepattern(path).call();
187 		ByteArrayOutputStream out = new ByteArrayOutputStream();
188 		try {
189 			git.commit().setMessage("commit")
190 					.setHookOutputStream(new PrintStream(out)).call();
191 		} catch (AbortedByHookException e) {
192 			fail("unexpected hook failure");
193 		}
194 		assertEquals("unexpected hook output",
195 				"test pre-commit\ntest commit-msg .git/COMMIT_EDITMSG\ntest post-commit\n",
196 				out.toString("UTF-8"));
197 	}
198 
199 	@Test
200 	public void testRunHook() throws Exception {
201 		assumeSupportedPlatform();
202 
203 		writeHookFile(PreCommitHook.NAME,
204 				"#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\n"
205 						+ "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\"");
206 		ByteArrayOutputStream out = new ByteArrayOutputStream();
207 		ByteArrayOutputStream err = new ByteArrayOutputStream();
208 		ProcessResult res = FS.DETECTED.runHookIfPresent(db,
209 				PreCommitHook.NAME,
210 				new String[] {
211 				"arg1", "arg2" },
212 				new PrintStream(out), new PrintStream(err), "stdin");
213 
214 		assertEquals("unexpected hook output",
215 				"test arg1 arg2\nstdin\n" + db.getDirectory().getAbsolutePath()
216 						+ '\n' + 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 	@Test
226 	public void testRunHookHooksPathRelative() throws Exception {
227 		assumeSupportedPlatform();
228 
229 		writeHookFile(PreCommitHook.NAME,
230 				"#!/bin/sh\necho \"Wrong hook $1 $2\"\nread INPUT\necho $INPUT\n"
231 						+ "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\"");
232 		writeHookFile("../../" + PreCommitHook.NAME,
233 				"#!/bin/sh\necho \"test $1 $2\"\nread INPUT\necho $INPUT\n"
234 						+ "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\"");
235 		StoredConfig cfg = db.getConfig();
236 		cfg.load();
237 		cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
238 				ConfigConstants.CONFIG_KEY_HOOKS_PATH, ".");
239 		cfg.save();
240 		try (ByteArrayOutputStream out = new ByteArrayOutputStream();
241 				ByteArrayOutputStream err = new ByteArrayOutputStream()) {
242 			ProcessResult res = FS.DETECTED.runHookIfPresent(db,
243 					PreCommitHook.NAME, new String[] { "arg1", "arg2" },
244 					new PrintStream(out), new PrintStream(err), "stdin");
245 
246 			assertEquals("unexpected hook output",
247 					"test arg1 arg2\nstdin\n"
248 							+ db.getDirectory().getAbsolutePath() + '\n'
249 							+ db.getWorkTree().getAbsolutePath() + '\n',
250 					out.toString("UTF-8"));
251 			assertEquals("unexpected output on stderr stream", "stderr\n",
252 					err.toString("UTF-8"));
253 			assertEquals("unexpected exit code", 0, res.getExitCode());
254 			assertEquals("unexpected process status", ProcessResult.Status.OK,
255 					res.getStatus());
256 		}
257 	}
258 
259 	@Test
260 	public void testRunHookHooksPathAbsolute() throws Exception {
261 		assumeSupportedPlatform();
262 
263 		writeHookFile(PreCommitHook.NAME,
264 				"#!/bin/sh\necho \"Wrong hook $1 $2\"\nread INPUT\necho $INPUT\n"
265 						+ "echo $GIT_DIR\necho $GIT_WORK_TREE\necho 1>&2 \"stderr\"");
266 		writeHookFile("../../" + 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 				db.getWorkTree().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());
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 }