View Javadoc
1   /*
2    * Copyright (C) 2010, Christian Halstrick <christian.halstrick@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.api;
44  
45  import static org.junit.Assert.assertEquals;
46  import static org.junit.Assert.assertFalse;
47  import static org.junit.Assert.assertTrue;
48  import static org.junit.Assert.fail;
49  import static org.junit.Assume.assumeFalse;
50  
51  import java.io.File;
52  import java.io.IOException;
53  import java.io.PrintWriter;
54  
55  import org.eclipse.jgit.api.errors.GitAPIException;
56  import org.eclipse.jgit.api.errors.JGitInternalException;
57  import org.eclipse.jgit.api.errors.NoMessageException;
58  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
59  import org.eclipse.jgit.errors.MissingObjectException;
60  import org.eclipse.jgit.junit.RepositoryTestCase;
61  import org.eclipse.jgit.lib.Constants;
62  import org.eclipse.jgit.lib.ObjectId;
63  import org.eclipse.jgit.lib.PersonIdent;
64  import org.eclipse.jgit.lib.RefUpdate;
65  import org.eclipse.jgit.lib.ReflogReader;
66  import org.eclipse.jgit.revwalk.RevCommit;
67  import org.eclipse.jgit.treewalk.TreeWalk;
68  import org.eclipse.jgit.util.FS;
69  import org.eclipse.jgit.util.FileUtils;
70  import org.eclipse.jgit.util.RawParseUtils;
71  import org.junit.Test;
72  
73  /**
74   * Testing the git commit and log commands
75   */
76  public class CommitAndLogCommandTest extends RepositoryTestCase {
77  	@Test
78  	public void testSomeCommits() throws JGitInternalException, IOException,
79  			GitAPIException {
80  
81  		// do 4 commits
82  		try (Git git = new Git(db)) {
83  			git.commit().setMessage("initial commit").call();
84  			git.commit().setMessage("second commit").setCommitter(committer).call();
85  			git.commit().setMessage("third commit").setAuthor(author).call();
86  			git.commit().setMessage("fourth commit").setAuthor(author)
87  					.setCommitter(committer).call();
88  			Iterable<RevCommit> commits = git.log().call();
89  
90  			// check that all commits came in correctly
91  			PersonIdent defaultCommitter = new PersonIdent(db);
92  			PersonIdent expectedAuthors[] = new PersonIdent[] { defaultCommitter,
93  					committer, author, author };
94  			PersonIdent expectedCommitters[] = new PersonIdent[] {
95  					defaultCommitter, committer, defaultCommitter, committer };
96  			String expectedMessages[] = new String[] { "initial commit",
97  					"second commit", "third commit", "fourth commit" };
98  			int l = expectedAuthors.length - 1;
99  			for (RevCommit c : commits) {
100 				assertEquals(expectedAuthors[l].getName(), c.getAuthorIdent()
101 						.getName());
102 				assertEquals(expectedCommitters[l].getName(), c.getCommitterIdent()
103 						.getName());
104 				assertEquals(c.getFullMessage(), expectedMessages[l]);
105 				l--;
106 			}
107 			assertEquals(l, -1);
108 			ReflogReader reader = db.getReflogReader(Constants.HEAD);
109 			assertTrue(reader.getLastEntry().getComment().startsWith("commit:"));
110 			reader = db.getReflogReader(db.getBranch());
111 			assertTrue(reader.getLastEntry().getComment().startsWith("commit:"));
112 		}
113 	}
114 
115 	@Test
116 	public void testLogWithFilter() throws IOException, JGitInternalException,
117 			GitAPIException {
118 
119 		try (Git git = new Git(db)) {
120 			// create first file
121 			File file = new File(db.getWorkTree(), "a.txt");
122 			FileUtils.createNewFile(file);
123 			try (PrintWriter writer = new PrintWriter(file)) {
124 				writer.print("content1");
125 			}
126 
127 			// First commit - a.txt file
128 			git.add().addFilepattern("a.txt").call();
129 			git.commit().setMessage("commit1").setCommitter(committer).call();
130 
131 			// create second file
132 			file = new File(db.getWorkTree(), "b.txt");
133 			FileUtils.createNewFile(file);
134 			try (PrintWriter writer = new PrintWriter(file)) {
135 				writer.print("content2");
136 			}
137 
138 			// Second commit - b.txt file
139 			git.add().addFilepattern("b.txt").call();
140 			git.commit().setMessage("commit2").setCommitter(committer).call();
141 
142 			// First log - a.txt filter
143 			int count = 0;
144 			for (RevCommit c : git.log().addPath("a.txt").call()) {
145 				assertEquals("commit1", c.getFullMessage());
146 				count++;
147 			}
148 			assertEquals(1, count);
149 
150 			// Second log - b.txt filter
151 			count = 0;
152 			for (RevCommit c : git.log().addPath("b.txt").call()) {
153 				assertEquals("commit2", c.getFullMessage());
154 				count++;
155 			}
156 			assertEquals(1, count);
157 
158 			// Third log - without filter
159 			count = 0;
160 			for (RevCommit c : git.log().call()) {
161 				assertEquals(committer, c.getCommitterIdent());
162 				count++;
163 			}
164 			assertEquals(2, count);
165 		}
166 	}
167 
168 	// try to do a commit without specifying a message. Should fail!
169 	@Test
170 	public void testWrongParams() throws GitAPIException {
171 		try (Git git = new Git(db)) {
172 			git.commit().setAuthor(author).call();
173 			fail("Didn't get the expected exception");
174 		} catch (NoMessageException e) {
175 			// expected
176 		}
177 	}
178 
179 	// try to work with Commands after command has been invoked. Should throw
180 	// exceptions
181 	@Test
182 	public void testMultipleInvocations() throws GitAPIException {
183 		try (Git git = new Git(db)) {
184 			CommitCommand commitCmd = git.commit();
185 			commitCmd.setMessage("initial commit").call();
186 			try {
187 				// check that setters can't be called after invocation
188 				commitCmd.setAuthor(author);
189 				fail("didn't catch the expected exception");
190 			} catch (IllegalStateException e) {
191 				// expected
192 			}
193 			LogCommand logCmd = git.log();
194 			logCmd.call();
195 			try {
196 				// check that call can't be called twice
197 				logCmd.call();
198 				fail("didn't catch the expected exception");
199 			} catch (IllegalStateException e) {
200 				// expected
201 			}
202 		}
203 	}
204 
205 	@Test
206 	public void testMergeEmptyBranches() throws IOException,
207 			JGitInternalException, GitAPIException {
208 		try (Git git = new Git(db)) {
209 			git.commit().setMessage("initial commit").call();
210 			RefUpdate r = db.updateRef("refs/heads/side");
211 			r.setNewObjectId(db.resolve(Constants.HEAD));
212 			assertEquals(r.forceUpdate(), RefUpdate.Result.NEW);
213 			RevCommit second = git.commit().setMessage("second commit").setCommitter(committer).call();
214 			db.updateRef(Constants.HEAD).link("refs/heads/side");
215 			RevCommit firstSide = git.commit().setMessage("first side commit").setAuthor(author).call();
216 
217 			write(new File(db.getDirectory(), Constants.MERGE_HEAD), ObjectId
218 					.toString(db.resolve("refs/heads/master")));
219 			write(new File(db.getDirectory(), Constants.MERGE_MSG), "merging");
220 
221 			RevCommit commit = git.commit().call();
222 			RevCommit[] parents = commit.getParents();
223 			assertEquals(parents[0], firstSide);
224 			assertEquals(parents[1], second);
225 			assertEquals(2, parents.length);
226 		}
227 	}
228 
229 	@Test
230 	public void testAddUnstagedChanges() throws IOException,
231 			JGitInternalException, GitAPIException {
232 		File file = new File(db.getWorkTree(), "a.txt");
233 		FileUtils.createNewFile(file);
234 		try (PrintWriter writer = new PrintWriter(file)) {
235 			writer.print("content");
236 		}
237 
238 		try (Git git = new Git(db)) {
239 			git.add().addFilepattern("a.txt").call();
240 			RevCommit commit = git.commit().setMessage("initial commit").call();
241 			TreeWalk tw = TreeWalk.forPath(db, "a.txt", commit.getTree());
242 			assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea",
243 					tw.getObjectId(0).getName());
244 
245 			try (PrintWriter writer = new PrintWriter(file)) {
246 				writer.print("content2");
247 			}
248 			commit = git.commit().setMessage("second commit").call();
249 			tw = TreeWalk.forPath(db, "a.txt", commit.getTree());
250 			assertEquals("6b584e8ece562ebffc15d38808cd6b98fc3d97ea",
251 					tw.getObjectId(0).getName());
252 
253 			commit = git.commit().setAll(true).setMessage("third commit")
254 					.setAll(true).call();
255 			tw = TreeWalk.forPath(db, "a.txt", commit.getTree());
256 			assertEquals("db00fd65b218578127ea51f3dffac701f12f486a",
257 					tw.getObjectId(0).getName());
258 		}
259 	}
260 
261 	@Test
262 	public void testModeChange() throws IOException, GitAPIException {
263 		assumeFalse(System.getProperty("os.name").startsWith("Windows"));// SKIP
264 		try (Git git = new Git(db)) {
265 			// create file
266 			File file = new File(db.getWorkTree(), "a.txt");
267 			FileUtils.createNewFile(file);
268 			try (PrintWriter writer = new PrintWriter(file)) {
269 				writer.print("content1");
270 			}
271 
272 			// First commit - a.txt file
273 			git.add().addFilepattern("a.txt").call();
274 			git.commit().setMessage("commit1").setCommitter(committer).call();
275 
276 			// pure mode change should be committable
277 			FS fs = db.getFS();
278 			fs.setExecute(file, true);
279 			git.add().addFilepattern("a.txt").call();
280 			git.commit().setMessage("mode change").setCommitter(committer).call();
281 
282 			// pure mode change should be committable with -o option
283 			fs.setExecute(file, false);
284 			git.add().addFilepattern("a.txt").call();
285 			git.commit().setMessage("mode change").setCommitter(committer)
286 					.setOnly("a.txt").call();
287 		}
288 	}
289 
290 	@Test
291 	public void testCommitRange() throws GitAPIException,
292 			JGitInternalException, MissingObjectException,
293 			IncorrectObjectTypeException {
294 		// do 4 commits and set the range to the second and fourth one
295 		try (Git git = new Git(db)) {
296 			git.commit().setMessage("first commit").call();
297 			RevCommit second = git.commit().setMessage("second commit")
298 					.setCommitter(committer).call();
299 			git.commit().setMessage("third commit").setAuthor(author).call();
300 			RevCommit last = git.commit().setMessage("fourth commit").setAuthor(
301 					author)
302 					.setCommitter(committer).call();
303 			Iterable<RevCommit> commits = git.log().addRange(second.getId(),
304 					last.getId()).call();
305 
306 			// check that we have the third and fourth commit
307 			PersonIdent defaultCommitter = new PersonIdent(db);
308 			PersonIdent expectedAuthors[] = new PersonIdent[] { author, author };
309 			PersonIdent expectedCommitters[] = new PersonIdent[] {
310 					defaultCommitter, committer };
311 			String expectedMessages[] = new String[] { "third commit",
312 					"fourth commit" };
313 			int l = expectedAuthors.length - 1;
314 			for (RevCommit c : commits) {
315 				assertEquals(expectedAuthors[l].getName(), c.getAuthorIdent()
316 						.getName());
317 				assertEquals(expectedCommitters[l].getName(), c.getCommitterIdent()
318 						.getName());
319 				assertEquals(c.getFullMessage(), expectedMessages[l]);
320 				l--;
321 			}
322 			assertEquals(l, -1);
323 		}
324 	}
325 
326 	@Test
327 	public void testCommitAmend() throws JGitInternalException, IOException,
328 			GitAPIException {
329 		try (Git git = new Git(db)) {
330 			git.commit().setMessage("first comit").call(); // typo
331 			git.commit().setAmend(true).setMessage("first commit").call();
332 
333 			Iterable<RevCommit> commits = git.log().call();
334 			int c = 0;
335 			for (RevCommit commit : commits) {
336 				assertEquals("first commit", commit.getFullMessage());
337 				c++;
338 			}
339 			assertEquals(1, c);
340 			ReflogReader reader = db.getReflogReader(Constants.HEAD);
341 			assertTrue(reader.getLastEntry().getComment()
342 					.startsWith("commit (amend):"));
343 			reader = db.getReflogReader(db.getBranch());
344 			assertTrue(reader.getLastEntry().getComment()
345 					.startsWith("commit (amend):"));
346 		}
347 	}
348 
349 	@Test
350 	public void testInsertChangeId() throws JGitInternalException,
351 			GitAPIException {
352 		try (Git git = new Git(db)) {
353 			String messageHeader = "Some header line\n\nSome detail explanation\n";
354 			String changeIdTemplate = "\nChange-Id: I"
355 					+ ObjectId.zeroId().getName() + "\n";
356 			String messageFooter = "Some foooter lines\nAnother footer line\n";
357 			RevCommit commit = git.commit().setMessage(
358 					messageHeader + messageFooter)
359 					.setInsertChangeId(true).call();
360 			// we should find a real change id (at the end of the file)
361 			byte[] chars = commit.getFullMessage().getBytes();
362 			int lastLineBegin = RawParseUtils.prevLF(chars, chars.length - 2);
363 			String lastLine = RawParseUtils.decode(chars, lastLineBegin + 1,
364 					chars.length);
365 			assertTrue(lastLine.contains("Change-Id:"));
366 			assertFalse(lastLine.contains(
367 					"Change-Id: I" + ObjectId.zeroId().getName()));
368 
369 			commit = git.commit().setMessage(
370 					messageHeader + changeIdTemplate + messageFooter)
371 					.setInsertChangeId(true).call();
372 			// we should find a real change id (in the line as dictated by the
373 			// template)
374 			chars = commit.getFullMessage().getBytes();
375 			int lineStart = 0;
376 			int lineEnd = 0;
377 			for (int i = 0; i < 4; i++) {
378 				lineStart = RawParseUtils.nextLF(chars, lineStart);
379 			}
380 			lineEnd = RawParseUtils.nextLF(chars, lineStart);
381 
382 			String line = RawParseUtils.decode(chars, lineStart, lineEnd);
383 
384 			assertTrue(line.contains("Change-Id:"));
385 			assertFalse(line.contains(
386 					"Change-Id: I" + ObjectId.zeroId().getName()));
387 
388 			commit = git.commit().setMessage(
389 					messageHeader + changeIdTemplate + messageFooter)
390 					.setInsertChangeId(false).call();
391 			// we should find the untouched template
392 			chars = commit.getFullMessage().getBytes();
393 			lineStart = 0;
394 			lineEnd = 0;
395 			for (int i = 0; i < 4; i++) {
396 				lineStart = RawParseUtils.nextLF(chars, lineStart);
397 			}
398 			lineEnd = RawParseUtils.nextLF(chars, lineStart);
399 
400 			line = RawParseUtils.decode(chars, lineStart, lineEnd);
401 
402 			assertTrue(commit.getFullMessage().contains(
403 					"Change-Id: I" + ObjectId.zeroId().getName()));
404 		}
405 	}
406 }