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