123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465 |
- /*
- * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- package org.eclipse.jgit.api;
-
- import java.io.BufferedReader;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.InputStreamReader;
-
- import org.eclipse.jgit.api.RebaseCommand.Operation;
- import org.eclipse.jgit.api.RebaseResult.Status;
- import org.eclipse.jgit.api.errors.RefNotFoundException;
- import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
- import org.eclipse.jgit.dircache.DirCacheCheckout;
- import org.eclipse.jgit.lib.Constants;
- import org.eclipse.jgit.lib.ObjectId;
- import org.eclipse.jgit.lib.RefUpdate;
- import org.eclipse.jgit.lib.RepositoryState;
- import org.eclipse.jgit.lib.RepositoryTestCase;
- import org.eclipse.jgit.revwalk.RevCommit;
- import org.eclipse.jgit.revwalk.RevWalk;
-
- public class RebaseCommandTest extends RepositoryTestCase {
- private void createBranch(ObjectId objectId, String branchName)
- throws IOException {
- RefUpdate updateRef = db.updateRef(branchName);
- updateRef.setNewObjectId(objectId);
- updateRef.update();
- }
-
- private void checkoutBranch(String branchName)
- throws IllegalStateException, IOException {
- RevWalk walk = new RevWalk(db);
- RevCommit head = walk.parseCommit(db.resolve(Constants.HEAD));
- RevCommit branch = walk.parseCommit(db.resolve(branchName));
- DirCacheCheckout dco = new DirCacheCheckout(db, head.getTree().getId(),
- db.lockDirCache(), branch.getTree().getId());
- dco.setFailOnConflict(true);
- dco.checkout();
- walk.release();
- // update the HEAD
- RefUpdate refUpdate = db.updateRef(Constants.HEAD);
- refUpdate.link(branchName);
- }
-
- private void checkoutCommit(RevCommit commit) throws IllegalStateException,
- IOException {
- RevWalk walk = new RevWalk(db);
- RevCommit head = walk.parseCommit(db.resolve(Constants.HEAD));
- DirCacheCheckout dco = new DirCacheCheckout(db, head.getTree(),
- db.lockDirCache(), commit.getTree());
- dco.setFailOnConflict(true);
- dco.checkout();
- walk.release();
- // update the HEAD
- RefUpdate refUpdate = db.updateRef(Constants.HEAD, true);
- refUpdate.setNewObjectId(commit);
- refUpdate.forceUpdate();
- }
-
- public void testFastForwardWithNewFile() throws Exception {
- Git git = new Git(db);
-
- // create file1 on master
- writeTrashFile("file1", "file1");
- git.add().addFilepattern("file1").call();
- RevCommit first = git.commit().setMessage("Add file1").call();
-
- assertTrue(new File(db.getWorkTree(), "file1").exists());
- // create a topic branch
- createBranch(first, "refs/heads/topic");
- // create file2 on master
- writeTrashFile("file2", "file2");
- git.add().addFilepattern("file2").call();
- git.commit().setMessage("Add file2").call();
- assertTrue(new File(db.getWorkTree(), "file2").exists());
-
- checkoutBranch("refs/heads/topic");
- assertFalse(new File(db.getWorkTree(), "file2").exists());
-
- RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
- assertEquals(Status.UP_TO_DATE, res.getStatus());
- }
-
- public void testUpToDate() throws Exception {
- Git git = new Git(db);
-
- // create file1 on master
- writeTrashFile("file1", "file1");
- git.add().addFilepattern("file1").call();
- RevCommit first = git.commit().setMessage("Add file1").call();
-
- assertTrue(new File(db.getWorkTree(), "file1").exists());
-
- RebaseResult res = git.rebase().setUpstream(first).call();
- assertEquals(Status.UP_TO_DATE, res.getStatus());
- }
-
- public void testUnknownUpstream() throws Exception {
- Git git = new Git(db);
-
- // create file1 on master
- writeTrashFile("file1", "file1");
- git.add().addFilepattern("file1").call();
- git.commit().setMessage("Add file1").call();
-
- assertTrue(new File(db.getWorkTree(), "file1").exists());
-
- try {
- git.rebase().setUpstream("refs/heads/xyz")
- .call();
- fail("expected exception was not thrown");
- } catch (RefNotFoundException e) {
- // expected exception
- }
- }
-
- public void testConflictFreeWithSingleFile() throws Exception {
- Git git = new Git(db);
-
- // create file1 on master
- File theFile = writeTrashFile("file1", "1\n2\n3\n");
- git.add().addFilepattern("file1").call();
- RevCommit second = git.commit().setMessage("Add file1").call();
- assertTrue(new File(db.getWorkTree(), "file1").exists());
- // change first line in master and commit
- writeTrashFile("file1", "1master\n2\n3\n");
- checkFile(theFile, "1master\n2\n3\n");
- git.add().addFilepattern("file1").call();
- RevCommit lastMasterChange = git.commit().setMessage(
- "change file1 in master").call();
-
- // create a topic branch based on second commit
- createBranch(second, "refs/heads/topic");
- checkoutBranch("refs/heads/topic");
- // we have the old content again
- checkFile(theFile, "1\n2\n3\n");
-
- assertTrue(new File(db.getWorkTree(), "file1").exists());
- // change third line in topic branch
- writeTrashFile("file1", "1\n2\n3\ntopic\n");
- git.add().addFilepattern("file1").call();
- git.commit().setMessage("change file1 in topic").call();
-
- RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
- assertEquals(Status.OK, res.getStatus());
- checkFile(theFile, "1master\n2\n3\ntopic\n");
- // our old branch should be checked out again
- assertEquals("refs/heads/topic", db.getFullBranch());
- assertEquals(lastMasterChange, new RevWalk(db).parseCommit(
- db.resolve(Constants.HEAD)).getParent(0));
- }
-
- public void testDetachedHead() throws Exception {
- Git git = new Git(db);
-
- // create file1 on master
- File theFile = writeTrashFile("file1", "1\n2\n3\n");
- git.add().addFilepattern("file1").call();
- RevCommit second = git.commit().setMessage("Add file1").call();
- assertTrue(new File(db.getWorkTree(), "file1").exists());
- // change first line in master and commit
- writeTrashFile("file1", "1master\n2\n3\n");
- checkFile(theFile, "1master\n2\n3\n");
- git.add().addFilepattern("file1").call();
- RevCommit lastMasterChange = git.commit()
- .setMessage("change file1 in master").call();
-
- // create a topic branch based on second commit
- createBranch(second, "refs/heads/topic");
- checkoutBranch("refs/heads/topic");
- // we have the old content again
- checkFile(theFile, "1\n2\n3\n");
-
- assertTrue(new File(db.getWorkTree(), "file1").exists());
- // change third line in topic branch
- writeTrashFile("file1", "1\n2\n3\ntopic\n");
- git.add().addFilepattern("file1").call();
- RevCommit topicCommit = git.commit()
- .setMessage("change file1 in topic").call();
- checkoutBranch("refs/heads/master");
- checkoutCommit(topicCommit);
- assertEquals(topicCommit.getId().getName(), db.getFullBranch());
-
- RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
- assertEquals(Status.OK, res.getStatus());
- checkFile(theFile, "1master\n2\n3\ntopic\n");
- assertEquals(lastMasterChange,
- new RevWalk(db).parseCommit(db.resolve(Constants.HEAD))
- .getParent(0));
-
- }
-
- public void testFilesAddedFromTwoBranches() throws Exception {
- Git git = new Git(db);
-
- // create file1 on master
- writeTrashFile("file1", "file1");
- git.add().addFilepattern("file1").call();
- RevCommit masterCommit = git.commit().setMessage("Add file1 to master")
- .call();
-
- // create a branch named file2 and add file2
- createBranch(masterCommit, "refs/heads/file2");
- checkoutBranch("refs/heads/file2");
- writeTrashFile("file2", "file2");
- git.add().addFilepattern("file2").call();
- RevCommit addFile2 = git.commit().setMessage(
- "Add file2 to branch file2").call();
-
- // create a branch named file3 and add file3
- createBranch(masterCommit, "refs/heads/file3");
- checkoutBranch("refs/heads/file3");
- writeTrashFile("file3", "file3");
- git.add().addFilepattern("file3").call();
- git.commit().setMessage("Add file3 to branch file3").call();
-
- assertTrue(new File(db.getWorkTree(), "file1").exists());
- assertFalse(new File(db.getWorkTree(), "file2").exists());
- assertTrue(new File(db.getWorkTree(), "file3").exists());
-
- RebaseResult res = git.rebase().setUpstream("refs/heads/file2").call();
- assertEquals(Status.OK, res.getStatus());
-
- assertTrue(new File(db.getWorkTree(), "file1").exists());
- assertTrue(new File(db.getWorkTree(), "file2").exists());
- assertTrue(new File(db.getWorkTree(), "file3").exists());
-
- // our old branch should be checked out again
- assertEquals("refs/heads/file3", db.getFullBranch());
- assertEquals(addFile2, new RevWalk(db).parseCommit(
- db.resolve(Constants.HEAD)).getParent(0));
-
- checkoutBranch("refs/heads/file2");
- assertTrue(new File(db.getWorkTree(), "file1").exists());
- assertTrue(new File(db.getWorkTree(), "file2").exists());
- assertFalse(new File(db.getWorkTree(), "file3").exists());
- }
-
- public void testAbortOnConflict() throws Exception {
- Git git = new Git(db);
-
- // create file1 on master
- File theFile = writeTrashFile("file1", "1\n2\n3\n");
- git.add().addFilepattern("file1").call();
- RevCommit firstInMaster = git.commit().setMessage("Add file1").call();
- assertTrue(new File(db.getWorkTree(), "file1").exists());
- // change first line in master and commit
- writeTrashFile("file1", "1master\n2\n3\n");
- checkFile(theFile, "1master\n2\n3\n");
- git.add().addFilepattern("file1").call();
- git.commit().setMessage("change file1 in master").call();
-
- // create a topic branch based on second commit
- createBranch(firstInMaster, "refs/heads/topic");
- checkoutBranch("refs/heads/topic");
- // we have the old content again
- checkFile(theFile, "1\n2\n3\n");
-
- assertTrue(new File(db.getWorkTree(), "file1").exists());
- // add a line (non-conflicting)
- writeTrashFile("file1", "1\n2\n3\ntopic4\n");
- git.add().addFilepattern("file1").call();
- git.commit().setMessage("add a line to file1 in topic").call();
-
- // change first line (conflicting)
- writeTrashFile("file1", "1topic\n2\n3\ntopic4\n");
- git.add().addFilepattern("file1").call();
- RevCommit conflicting = git.commit()
- .setMessage("change file1 in topic").call();
-
- // change second line (not conflicting)
- writeTrashFile("file1", "1topic\n2topic\n3\ntopic4\n");
- git.add().addFilepattern("file1").call();
- RevCommit lastTopicCommit = git.commit().setMessage(
- "change file1 in topic again").call();
-
- RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
- assertEquals(Status.STOPPED, res.getStatus());
- assertEquals(conflicting, res.getCurrentCommit());
- checkFile(theFile,
- "<<<<<<< OURS\n1master\n=======\n1topic\n>>>>>>> THEIRS\n2\n3\ntopic4\n");
-
- assertEquals(RepositoryState.REBASING_INTERACTIVE, db
- .getRepositoryState());
- assertTrue(new File(db.getDirectory(), "rebase-merge").exists());
- // the first one should be included, so we should have left two picks in
- // the file
- assertEquals(1, countPicks());
-
- // rebase should not succeed in this state
- try {
- git.rebase().setUpstream("refs/heads/master").call();
- fail("Expected exception was not thrown");
- } catch (WrongRepositoryStateException e) {
- // expected
- }
-
- // abort should reset to topic branch
- res = git.rebase().setOperation(Operation.ABORT).call();
- assertEquals(res.getStatus(), Status.ABORTED);
- assertEquals("refs/heads/topic", db.getFullBranch());
- checkFile(theFile, "1topic\n2topic\n3\ntopic4\n");
- RevWalk rw = new RevWalk(db);
- assertEquals(lastTopicCommit, rw
- .parseCommit(db.resolve(Constants.HEAD)));
- assertEquals(RepositoryState.SAFE, db.getRepositoryState());
-
- // rebase- dir in .git must be deleted
- assertFalse(new File(db.getDirectory(), "rebase-merge").exists());
- }
-
- public void testAbortOnConflictFileCreationAndDeletion() throws Exception {
- Git git = new Git(db);
-
- // create file1 on master
- writeTrashFile("file1", "Hello World");
- git.add().addFilepattern("file1").call();
- // create file2 on master
- File file2 = writeTrashFile("file2", "Hello World 2");
- git.add().addFilepattern("file2").call();
- // create file3 on master
- File file3 = writeTrashFile("file3", "Hello World 3");
- git.add().addFilepattern("file3").call();
-
- RevCommit firstInMaster = git.commit()
- .setMessage("Add file 1, 2 and 3").call();
-
- // create file4 on master
- File file4 = writeTrashFile("file4", "Hello World 4");
- git.add().addFilepattern("file4").call();
-
- deleteTrashFile("file2");
- git.add().setUpdate(true).addFilepattern("file2").call();
- // create folder folder6 on topic (conflicts with file folder6 on topic
- // later on)
- writeTrashFile("folder6/file1", "Hello World folder6");
- git.add().addFilepattern("folder6/file1").call();
-
- git.commit()
- .setMessage(
- "Add file 4 and folder folder6, delete file2 on master")
- .call();
-
- // create a topic branch based on second commit
- createBranch(firstInMaster, "refs/heads/topic");
- checkoutBranch("refs/heads/topic");
-
- deleteTrashFile("file3");
- git.add().setUpdate(true).addFilepattern("file3").call();
- // create file5 on topic
- File file5 = writeTrashFile("file5", "Hello World 5");
- git.add().addFilepattern("file5").call();
- git.commit().setMessage("Delete file3 and add file5 in topic").call();
-
- // create file folder6 on topic (conflicts with folder6 on master)
- writeTrashFile("folder6", "Hello World 6");
- git.add().addFilepattern("folder6").call();
- // create file7 on topic
- File file7 = writeTrashFile("file7", "Hello World 7");
- git.add().addFilepattern("file7").call();
-
- deleteTrashFile("file5");
- git.add().setUpdate(true).addFilepattern("file5").call();
- RevCommit conflicting = git.commit()
- .setMessage("Delete file5, add file folder6 and file7 in topic")
- .call();
-
- RebaseResult res = git.rebase().setUpstream("refs/heads/master").call();
- assertEquals(Status.STOPPED, res.getStatus());
- assertEquals(conflicting, res.getCurrentCommit());
-
- assertEquals(RepositoryState.REBASING_INTERACTIVE, db
- .getRepositoryState());
- assertTrue(new File(db.getDirectory(), "rebase-merge").exists());
- // the first one should be included, so we should have left two picks in
- // the file
- assertEquals(0, countPicks());
-
- assertFalse(file2.exists());
- assertFalse(file3.exists());
- assertTrue(file4.exists());
- assertFalse(file5.exists());
- assertTrue(file7.exists());
-
- // abort should reset to topic branch
- res = git.rebase().setOperation(Operation.ABORT).call();
- assertEquals(res.getStatus(), Status.ABORTED);
- assertEquals("refs/heads/topic", db.getFullBranch());
- RevWalk rw = new RevWalk(db);
- assertEquals(conflicting,
- rw.parseCommit(db.resolve(Constants.HEAD)));
- assertEquals(RepositoryState.SAFE, db.getRepositoryState());
-
- // rebase- dir in .git must be deleted
- assertFalse(new File(db.getDirectory(), "rebase-merge").exists());
-
- assertTrue(file2.exists());
- assertFalse(file3.exists());
- assertFalse(file4.exists());
- assertFalse(file5.exists());
- assertTrue(file7.exists());
-
- }
-
- private int countPicks() throws IOException {
- int count = 0;
- File todoFile = new File(db.getDirectory(),
- "rebase-merge/git-rebase-todo");
- BufferedReader br = new BufferedReader(new InputStreamReader(
- new FileInputStream(todoFile), "UTF-8"));
- try {
- String line = br.readLine();
- while (line != null) {
- if (line.startsWith("pick "))
- count++;
- line = br.readLine();
- }
- return count;
- } finally {
- br.close();
- }
- }
- }
|