diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java new file mode 100644 index 000000000..07dc56003 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/BranchCommandTest.java @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2010, Mathias Kinzler + * 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.util.List; + +import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode; +import org.eclipse.jgit.api.ListBranchCommand.ListMode; +import org.eclipse.jgit.api.errors.CannotDeleteCurrentBranchException; +import org.eclipse.jgit.api.errors.DetachedHeadException; +import org.eclipse.jgit.api.errors.InvalidRefNameException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.api.errors.NotMergedException; +import org.eclipse.jgit.api.errors.RefAlreadyExistsException; +import org.eclipse.jgit.api.errors.RefNotFoundException; +import org.eclipse.jgit.errors.AmbiguousObjectException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryTestCase; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.FetchResult; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; + +public class BranchCommandTest extends RepositoryTestCase { + private Git git; + + RevCommit initialCommit; + + RevCommit secondCommit; + + @Override + protected void setUp() throws Exception { + super.setUp(); + git = new Git(db); + // checkout master + git.commit().setMessage("initial commit").call(); + // commit something + writeTrashFile("Test.txt", "Hello world"); + git.add().addFilepattern("Test.txt").call(); + initialCommit = git.commit().setMessage("Initial commit").call(); + writeTrashFile("Test.txt", "Some change"); + git.add().addFilepattern("Test.txt").call(); + secondCommit = git.commit().setMessage("Second commit").call(); + // create a master branch + RefUpdate rup = db.updateRef("refs/heads/master"); + rup.setNewObjectId(initialCommit.getId()); + rup.setForceUpdate(true); + rup.update(); + } + + private Git setUpRepoWithRemote() throws Exception { + Repository remoteRepository = createWorkRepository(); + Git remoteGit = new Git(remoteRepository); + // commit something + writeTrashFile("Test.txt", "Hello world"); + remoteGit.add().addFilepattern("Test.txt").call(); + initialCommit = remoteGit.commit().setMessage("Initial commit").call(); + writeTrashFile("Test.txt", "Some change"); + remoteGit.add().addFilepattern("Test.txt").call(); + secondCommit = remoteGit.commit().setMessage("Second commit").call(); + // create a master branch + RefUpdate rup = remoteRepository.updateRef("refs/heads/master"); + rup.setNewObjectId(initialCommit.getId()); + rup.forceUpdate(); + + Repository localRepository = createWorkRepository(); + Git localGit = new Git(localRepository); + StoredConfig config = localRepository.getConfig(); + RemoteConfig rc = new RemoteConfig(config, "origin"); + rc.addURI(new URIish(remoteRepository.getDirectory().getPath())); + rc.addFetchRefSpec(new RefSpec("+refs/heads/*:refs/remotes/origin/*")); + rc.update(config); + config.save(); + FetchResult res = localGit.fetch().setRemote("origin").call(); + assertFalse(res.getTrackingRefUpdates().isEmpty()); + rup = localRepository.updateRef("refs/heads/master"); + rup.setNewObjectId(initialCommit.getId()); + rup.forceUpdate(); + rup = localRepository.updateRef(Constants.HEAD); + rup.link("refs/heads/master"); + rup.setNewObjectId(initialCommit.getId()); + rup.update(); + return localGit; + } + + public void testCreateAndList() throws Exception { + int localBefore; + int remoteBefore; + int allBefore; + + // invalid name not allowed + try { + git.branchCreate().setName("In va lid").call(); + fail("Create branch with invalid ref name should fail"); + } catch (InvalidRefNameException e) { + // expected + } + // existing name not allowed w/o force + try { + git.branchCreate().setName("master").call(); + fail("Create branch with existing ref name should fail"); + } catch (RefAlreadyExistsException e) { + // expected + } + + localBefore = git.branchList().call().size(); + remoteBefore = git.branchList().setListMode(ListMode.REMOTE).call() + .size(); + allBefore = git.branchList().setListMode(ListMode.ALL).call().size(); + + assertEquals(localBefore + remoteBefore, allBefore); + Ref newBranch = createBranch(git, "NewForTestList", false, "master", + null); + assertEquals("refs/heads/NewForTestList", newBranch.getName()); + + assertEquals(1, git.branchList().call().size() - localBefore); + assertEquals(0, git.branchList().setListMode(ListMode.REMOTE).call() + .size() + - remoteBefore); + assertEquals(1, git.branchList().setListMode(ListMode.ALL).call() + .size() + - allBefore); + // we can only create local branches + newBranch = createBranch(git, + "refs/remotes/origin/NewRemoteForTestList", false, "master", + null); + assertEquals("refs/heads/refs/remotes/origin/NewRemoteForTestList", + newBranch.getName()); + assertEquals(2, git.branchList().call().size() - localBefore); + assertEquals(0, git.branchList().setListMode(ListMode.REMOTE).call() + .size() + - remoteBefore); + assertEquals(2, git.branchList().setListMode(ListMode.ALL).call() + .size() + - allBefore); + } + + public void testCreateFromCommit() throws Exception { + Ref branch = git.branchCreate().setName("FromInitial").setStartPoint( + initialCommit).call(); + assertEquals(initialCommit.getId(), branch.getObjectId()); + branch = git.branchCreate().setName("FromInitial2").setStartPoint( + initialCommit.getId().name()).call(); + assertEquals(initialCommit.getId(), branch.getObjectId()); + try { + git.branchCreate().setName("FromInitial").setStartPoint( + secondCommit).call(); + } catch (RefAlreadyExistsException e) { + // expected + } + branch = git.branchCreate().setName("FromInitial").setStartPoint( + secondCommit).setForce(true).call(); + assertEquals(secondCommit.getId(), branch.getObjectId()); + } + + public void testCreateForce() throws Exception { + // using commits + Ref newBranch = createBranch(git, "NewForce", false, secondCommit + .getId().name(), null); + assertEquals(newBranch.getTarget().getObjectId(), secondCommit.getId()); + try { + newBranch = createBranch(git, "NewForce", false, initialCommit + .getId().name(), null); + fail("Should have failed"); + } catch (RefAlreadyExistsException e) { + // expected + } + newBranch = createBranch(git, "NewForce", true, initialCommit.getId() + .name(), null); + assertEquals(newBranch.getTarget().getObjectId(), initialCommit.getId()); + git.branchDelete().setBranchNames("NewForce").call(); + // using names + + git.branchCreate().setName("NewForce").setStartPoint("master").call(); + assertEquals(newBranch.getTarget().getObjectId(), initialCommit.getId()); + try { + git.branchCreate().setName("NewForce").setStartPoint("master") + .call(); + fail("Should have failed"); + } catch (RefAlreadyExistsException e) { + // expected + } + git.branchCreate().setName("NewForce").setStartPoint("master") + .setForce(true).call(); + assertEquals(newBranch.getTarget().getObjectId(), initialCommit.getId()); + } + + public void testDelete() throws Exception { + createBranch(git, "ForDelete", false, "master", null); + git.branchDelete().setBranchNames("ForDelete").call(); + // now point the branch to a non-merged commit + createBranch(git, "ForDelete", false, secondCommit.getId().name(), null); + try { + git.branchDelete().setBranchNames("ForDelete").call(); + fail("Deletion of a non-merged branch without force should have failed"); + } catch (NotMergedException e) { + // expected + } + List deleted = git.branchDelete().setBranchNames("ForDelete") + .setForce(true).call(); + assertEquals(1, deleted.size()); + assertEquals(Constants.R_HEADS + "ForDelete", deleted.get(0)); + createBranch(git, "ForDelete", false, "master", null); + try { + createBranch(git, "ForDelete", false, "master", null); + fail("Repeated creation of same branch without force should fail"); + } catch (RefAlreadyExistsException e) { + // expected + } + // change starting point + Ref newBranch = createBranch(git, "ForDelete", true, initialCommit + .name(), null); + assertEquals(newBranch.getTarget().getObjectId(), initialCommit.getId()); + newBranch = createBranch(git, "ForDelete", true, secondCommit.name(), + null); + assertEquals(newBranch.getTarget().getObjectId(), secondCommit.getId()); + git.branchDelete().setBranchNames("ForDelete").setForce(true); + try { + git.branchDelete().setBranchNames("master").call(); + fail("Deletion of checked out branch without force should have failed"); + } catch (CannotDeleteCurrentBranchException e) { + // expected + } + try { + git.branchDelete().setBranchNames("master").setForce(true).call(); + fail("Deletion of checked out branch with force should have failed"); + } catch (CannotDeleteCurrentBranchException e) { + // expected + } + } + + public void testPullConfigRemoteBranch() throws Exception { + Git localGit = setUpRepoWithRemote(); + Ref remote = localGit.branchList().setListMode(ListMode.REMOTE).call() + .get(0); + assertEquals("refs/remotes/origin/master", remote.getName()); + // by default, we should create pull configuration + createBranch(localGit, "newFromRemote", false, remote.getName(), null); + assertEquals("origin", localGit.getRepository().getConfig().getString( + "branch", "newFromRemote", "remote")); + localGit.branchDelete().setBranchNames("newFromRemote").call(); + // the pull configuration should be gone after deletion + assertNull(localGit.getRepository().getConfig().getString("branch", + "newFromRemote", "remote")); + // use --no-track + createBranch(localGit, "newFromRemote", false, remote.getName(), + SetupUpstreamMode.NOTRACK); + assertNull(localGit.getRepository().getConfig().getString("branch", + "newFromRemote", "remote")); + localGit.branchDelete().setBranchNames("newFromRemote").call(); + } + + public void testPullConfigLocalBranch() throws Exception { + Git localGit = setUpRepoWithRemote(); + // by default, we should not create pull configuration + createBranch(localGit, "newFromMaster", false, "master", null); + assertNull(localGit.getRepository().getConfig().getString("branch", + "newFromMaster", "remote")); + localGit.branchDelete().setBranchNames("newFromMaster").call(); + // use --track + createBranch(localGit, "newFromMaster", false, "master", + SetupUpstreamMode.TRACK); + assertEquals(".", localGit.getRepository().getConfig().getString( + "branch", "newFromMaster", "remote")); + localGit.branchDelete().setBranchNames("newFromMaster").call(); + // the pull configuration should be gone after deletion + assertNull(localGit.getRepository().getConfig().getString("branch", + "newFromRemote", "remote")); + } + + public void testPullConfigRenameLocalBranch() throws Exception { + Git localGit = setUpRepoWithRemote(); + // by default, we should not create pull configuration + createBranch(localGit, "newFromMaster", false, "master", null); + assertNull(localGit.getRepository().getConfig().getString("branch", + "newFromMaster", "remote")); + localGit.branchDelete().setBranchNames("newFromMaster").call(); + // use --track + createBranch(localGit, "newFromMaster", false, "master", + SetupUpstreamMode.TRACK); + assertEquals(".", localGit.getRepository().getConfig().getString( + "branch", "newFromMaster", "remote")); + localGit.branchRename().setOldName("newFromMaster").setNewName( + "renamed").call(); + assertNull(".", localGit.getRepository().getConfig().getString( + "branch", "newFromMaster", "remote")); + assertEquals(".", localGit.getRepository().getConfig().getString( + "branch", "renamed", "remote")); + localGit.branchDelete().setBranchNames("renamed").call(); + // the pull configuration should be gone after deletion + assertNull(localGit.getRepository().getConfig().getString("branch", + "newFromRemote", "remote")); + } + + public void testRenameLocalBranch() throws Exception { + // null newName not allowed + try { + git.branchRename().call(); + } catch (InvalidRefNameException e) { + // expected + } + // invalid newName not allowed + try { + git.branchRename().setNewName("In va lid").call(); + } catch (InvalidRefNameException e) { + // expected + } + // not existing name not allowed + try { + git.branchRename().setOldName("notexistingbranch").setNewName( + "newname").call(); + } catch (RefNotFoundException e) { + // expected + } + // create some branch + createBranch(git, "existing", false, "master", null); + // a local branch + Ref branch = createBranch(git, "fromMasterForRename", false, "master", + null); + assertEquals(Constants.R_HEADS + "fromMasterForRename", branch + .getName()); + Ref renamed = git.branchRename().setOldName("fromMasterForRename") + .setNewName("newName").call(); + assertEquals(Constants.R_HEADS + "newName", renamed.getName()); + try { + git.branchRename().setOldName(renamed.getName()).setNewName( + "existing").call(); + fail("Should have failed"); + } catch (RefAlreadyExistsException e) { + // expected + } + try { + git.branchRename().setNewName("In va lid").call(); + fail("Rename with invalid ref name should fail"); + } catch (InvalidRefNameException e) { + // expected + } + // rename without old name and detached head not allowed + RefUpdate rup = git.getRepository().updateRef(Constants.HEAD, true); + rup.setNewObjectId(initialCommit); + rup.forceUpdate(); + try { + git.branchRename().setNewName("detached").call(); + } catch (DetachedHeadException e) { + // expected + } + } + + public void testRenameRemoteTrackingBranch() throws Exception { + Git localGit = setUpRepoWithRemote(); + Ref remoteBranch = localGit.branchList().setListMode(ListMode.REMOTE) + .call().get(0); + Ref renamed = localGit.branchRename() + .setOldName(remoteBranch.getName()).setNewName("newRemote") + .call(); + assertEquals(Constants.R_REMOTES + "newRemote", renamed.getName()); + } + + public Ref createBranch(Git actGit, String name, boolean force, + String startPoint, SetupUpstreamMode mode) + throws JGitInternalException, RefAlreadyExistsException, + AmbiguousObjectException, RefNotFoundException, + InvalidRefNameException { + CreateBranchCommand cmd = actGit.branchCreate(); + cmd.setName(name); + cmd.setForce(force); + cmd.setStartPoint(startPoint); + cmd.setUpstreamMode(mode); + return cmd.call(); + } +} diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index 2b0bf515d..3cc144a75 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties @@ -30,6 +30,7 @@ base64InputNotProperlyPadded=Base64 input not properly padded. baseLengthIncorrect=base length incorrect bareRepositoryNoWorkdirAndIndex=Bare Repository has neither a working tree, nor an index blobNotFound=Blob not found: {0} +branchNameInvalid=Branch name {0} is not allowed blobNotFoundForPath=Blob not found: {0} for path: {1} cannotBeCombined=Cannot be combined. cannotCombineTreeFilterWithRevFilter=Cannot combine TreeFilter {0} with RefFilter {1}. @@ -40,6 +41,7 @@ cannotConvertScriptToText=Cannot convert script to text cannotCreateConfig=cannot create config cannotCreateDirectory=Cannot create directory {0} cannotCreateHEAD=cannot create HEAD +cannotDeleteCheckedOutBranch=Branch {0} is checked out and can not be deleted cannotDeleteFile=Cannot delete file: {0} cannotDeleteStaleTrackingRef2=Cannot delete stale tracking ref {0}: {1} cannotDeleteStaleTrackingRef=Cannot delete stale tracking ref {0} @@ -118,8 +120,11 @@ couldNotRenameTemporaryIndexFileToIndex=Could not rename temporary index file to couldNotURLEncodeToUTF8=Could not URL encode to UTF-8 couldNotWriteFile=Could not write file {0} countingObjects=Counting objects +createBranchFailedUnknownReason=Create branch failed for unknown reason +createBranchUnexpectedResult=Create branch returned unexpected result {0} creatingDeltasIsNotImplemented=creating deltas is not implemented daemonAlreadyRunning=Daemon already running +deleteBranchUnexpectedResult=Delete branch returned unexpected result {0} deletingNotSupported=Deleting {0} not supported. destinationIsNotAWildcard=Destination is not a wildcard. detachedHeadDetected=HEAD is detached @@ -277,6 +282,7 @@ notFound=not found. notValid={0} not valid nothingToFetch=Nothing to fetch. nothingToPush=Nothing to push. +notMergedExceptionMessage=Branch was not deleted as it has not been merged yet; use the force option to delete it anyway objectAtHasBadZlibStream=Object at {0} in {1} has bad zlib stream objectAtPathDoesNotHaveId=Object at path "{0}" does not have an id assigned. All object ids must be assigned prior to writing a tree. objectIsCorrupt=Object {0} is corrupt: {1} @@ -317,6 +323,8 @@ rawLogMessageDoesNotParseAsLogEntry=Raw log message does not parse as log entry readTimedOut=Read timed out readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0} receivingObjects=Receiving objects +refAlreadExists=Ref {0} already exists +refNotResolved=Ref {0} can not be resolved refUpdateReturnCodeWas=RefUpdate return code was: {0} reflogsNotYetSupportedByRevisionParser=reflogs not yet supported by revision parser remoteConfigHasNoURIAssociated=Remote config "{0}" has no URIs associated @@ -324,6 +332,9 @@ remoteDoesNotHaveSpec=Remote does not have {0} available for fetch. remoteDoesNotSupportSmartHTTPPush=remote does not support smart HTTP push remoteHungUpUnexpectedly=remote hung up unexpectedly remoteNameCantBeNull=Remote name can't be null. +renameBranchFailedBecauseTag=Can not rename as Ref {0} is a tag +renameBranchFailedUnknownReason=Rename failed with unknown reason +renameBranchUnexpectedResult=Unexpected rename result {0} renamesAlreadyFound=Renames have already been found. renamesBreakingModifies=Breaking apart modified file pairs renamesFindingByContent=Finding renames by content similarity diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java index 6019b6a5d..93b970039 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java @@ -91,6 +91,7 @@ public class JGitText extends TranslationBundle { /***/ public String bareRepositoryNoWorkdirAndIndex; /***/ public String blobNotFound; /***/ public String blobNotFoundForPath; + /***/ public String branchNameInvalid; /***/ public String cannotBeCombined; /***/ public String cannotCombineTreeFilterWithRevFilter; /***/ public String cannotCommitOnARepoWithState; @@ -100,6 +101,7 @@ public class JGitText extends TranslationBundle { /***/ public String cannotCreateConfig; /***/ public String cannotCreateDirectory; /***/ public String cannotCreateHEAD; + /***/ public String cannotDeleteCheckedOutBranch; /***/ public String cannotDeleteFile; /***/ public String cannotDeleteStaleTrackingRef2; /***/ public String cannotDeleteStaleTrackingRef; @@ -178,8 +180,11 @@ public class JGitText extends TranslationBundle { /***/ public String couldNotURLEncodeToUTF8; /***/ public String couldNotWriteFile; /***/ public String countingObjects; + /***/ public String createBranchFailedUnknownReason; + /***/ public String createBranchUnexpectedResult; /***/ public String creatingDeltasIsNotImplemented; /***/ public String daemonAlreadyRunning; + /***/ public String deleteBranchUnexpectedResult; /***/ public String deletingNotSupported; /***/ public String destinationIsNotAWildcard; /***/ public String detachedHeadDetected; @@ -337,6 +342,7 @@ public class JGitText extends TranslationBundle { /***/ public String notValid; /***/ public String nothingToFetch; /***/ public String nothingToPush; + /***/ public String notMergedExceptionMessage; /***/ public String objectAtHasBadZlibStream; /***/ public String objectAtPathDoesNotHaveId; /***/ public String objectIsCorrupt; @@ -377,6 +383,8 @@ public class JGitText extends TranslationBundle { /***/ public String readTimedOut; /***/ public String readingObjectsFromLocalRepositoryFailed; /***/ public String receivingObjects; + /***/ public String refAlreadExists; + /***/ public String refNotResolved; /***/ public String refUpdateReturnCodeWas; /***/ public String reflogsNotYetSupportedByRevisionParser; /***/ public String remoteConfigHasNoURIAssociated; @@ -384,6 +392,9 @@ public class JGitText extends TranslationBundle { /***/ public String remoteDoesNotSupportSmartHTTPPush; /***/ public String remoteHungUpUnexpectedly; /***/ public String remoteNameCantBeNull; + /***/ public String renameBranchFailedBecauseTag; + /***/ public String renameBranchFailedUnknownReason; + /***/ public String renameBranchUnexpectedResult; /***/ public String renamesAlreadyFound; /***/ public String renamesBreakingModifies; /***/ public String renamesFindingByContent; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java new file mode 100644 index 000000000..4c584c33b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CreateBranchCommand.java @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2010, Mathias Kinzler + * Copyright (C) 2010, Chris Aniszczyk + * 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.IOException; +import java.text.MessageFormat; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.api.errors.InvalidRefNameException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.api.errors.RefAlreadyExistsException; +import org.eclipse.jgit.api.errors.RefNotFoundException; +import org.eclipse.jgit.errors.AmbiguousObjectException; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; + +/** + * Used to create a local branch. + * + * @see Git documentation about Branch + */ +public class CreateBranchCommand extends GitCommand { + private String name; + + private boolean force = false; + + private SetupUpstreamMode upstreamMode; + + private String startPoint; + + private RevCommit startCommit; + + /** + * The modes available for setting up the upstream configuration + * (corresponding to the --set-upstream, --track, --no-track options + * + */ + public enum SetupUpstreamMode { + /** + * Corresponds to the --track option + */ + TRACK, + /** + * Corresponds to the --no-track option + */ + NOTRACK, + /** + * Corresponds to the --set-upstream option + */ + SET_UPSTREAM; + } + + /** + * @param repo + */ + protected CreateBranchCommand(Repository repo) { + super(repo); + } + + /** + * @throws RefAlreadyExistsException + * when trying to create (without force) a branch with a name + * that already exists + * @throws RefNotFoundException + * if the start point can not be found + * @throws AmbiguousObjectException + * if the start point is ambiguous + * @throws InvalidRefNameException + * if the provided name is null or otherwise + * invalid + * @return the newly created branch + */ + public Ref call() throws JGitInternalException, RefAlreadyExistsException, + RefNotFoundException, AmbiguousObjectException, + InvalidRefNameException { + checkCallable(); + processOptions(); + try { + boolean exists = repo.getRef(name) != null; + if (!force && exists) + throw new RefAlreadyExistsException(MessageFormat.format( + JGitText.get().refAlreadExists, name)); + + ObjectId startAt = getStartPoint(); + String startPointFullName = null; + if (startPoint != null) { + Ref baseRef = repo.getRef(startPoint); + if (baseRef != null) + startPointFullName = baseRef.getName(); + } + + // determine whether we are based on a commit, + // a branch, or a tag and compose the reflog message + String refLogMessage; + String baseBranch = ""; + if (startPointFullName == null) { + String baseCommit; + if (startCommit != null) + baseCommit = startCommit.getShortMessage(); + else { + RevCommit commit = new RevWalk(repo).parseCommit(repo + .resolve(startPoint)); + baseCommit = commit.getShortMessage(); + } + if (exists) + refLogMessage = "branch: Reset start-point to commit " + + baseCommit; + else + refLogMessage = "branch: Created from commit " + baseCommit; + + } else if (startPointFullName.startsWith(Constants.R_HEADS) + || startPointFullName.startsWith(Constants.R_REMOTES)) { + baseBranch = startPointFullName; + if (exists) + refLogMessage = "branch: Reset start-point to branch " + + startPointFullName; // TODO + else + refLogMessage = "branch: Created from branch " + baseBranch; + } else { + if (exists) + refLogMessage = "branch: Reset start-point to tag " + + startPointFullName; + else + refLogMessage = "branch: Created from tag " + + startPointFullName; + } + + RefUpdate updateRef = repo.updateRef(Constants.R_HEADS + name); + updateRef.setNewObjectId(startAt); + updateRef.setRefLogMessage(refLogMessage, false); + Result updateResult; + if (exists && force) + updateResult = updateRef.forceUpdate(); + else + updateResult = updateRef.update(); + + setCallable(false); + + boolean ok = false; + switch (updateResult) { + case NEW: + ok = !exists; + break; + case NO_CHANGE: + case FAST_FORWARD: + case FORCED: + ok = exists; + break; + default: + break; + } + + if (!ok) + throw new JGitInternalException(MessageFormat.format(JGitText + .get().createBranchUnexpectedResult, updateResult + .name())); + + Ref result = repo.getRef(name); + if (result == null) + throw new JGitInternalException( + JGitText.get().createBranchFailedUnknownReason); + + if (baseBranch.length() == 0) { + return result; + } + + // if we are based on another branch, see + // if we need to configure upstream configuration: first check + // whether the setting was done explicitly + boolean doConfigure; + if (upstreamMode == SetupUpstreamMode.SET_UPSTREAM + || upstreamMode == SetupUpstreamMode.TRACK) + // explicitly set to configure + doConfigure = true; + else if (upstreamMode == SetupUpstreamMode.NOTRACK) + // explicitly set to not configure + doConfigure = false; + else { + // if there was no explicit setting, check the configuration + String autosetupflag = repo.getConfig().getString( + ConfigConstants.CONFIG_BRANCH_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOSETUPMERGE); + if ("false".equals(autosetupflag)) { + doConfigure = false; + } else if ("always".equals(autosetupflag)) { + doConfigure = true; + } else { + // in this case, the default is to configure + // only in case the base branch was a remote branch + doConfigure = baseBranch.startsWith(Constants.R_REMOTES); + } + } + + if (doConfigure) { + StoredConfig config = repo.getConfig(); + String[] tokens = baseBranch.split("/", 4); + boolean isRemote = tokens[1].equals("remotes"); + if (isRemote) { + // refs/remotes// + String remoteName = tokens[2]; + String branchName = tokens[3]; + config + .setString(ConfigConstants.CONFIG_BRANCH_SECTION, + name, ConfigConstants.CONFIG_KEY_REMOTE, + remoteName); + config.setString(ConfigConstants.CONFIG_BRANCH_SECTION, + name, ConfigConstants.CONFIG_KEY_MERGE, + Constants.R_HEADS + branchName); + } else { + // set "." as remote + config.setString(ConfigConstants.CONFIG_BRANCH_SECTION, + name, ConfigConstants.CONFIG_KEY_REMOTE, "."); + config.setString(ConfigConstants.CONFIG_BRANCH_SECTION, + name, ConfigConstants.CONFIG_KEY_MERGE, baseBranch); + } + config.save(); + } + return result; + } catch (AmbiguousObjectException e) { + throw e; + } catch (IOException ioe) { + throw new JGitInternalException(ioe.getMessage(), ioe); + } + } + + private ObjectId getStartPoint() throws AmbiguousObjectException, + RefNotFoundException, IOException { + if (startCommit != null) + return startCommit.getId(); + ObjectId result = null; + try { + if (startPoint == null) + result = repo.resolve(Constants.HEAD); + result = repo.resolve(startPoint); + } catch (AmbiguousObjectException e) { + throw e; + } + if (result == null) + throw new RefNotFoundException(MessageFormat.format( + JGitText.get().refNotResolved, + startPoint != null ? startPoint : Constants.HEAD)); + return result; + } + + private void processOptions() throws InvalidRefNameException { + if (name == null + || !Repository.isValidRefName(Constants.R_HEADS + name)) + throw new InvalidRefNameException(MessageFormat.format(JGitText + .get().branchNameInvalid, name == null ? "" : name)); + } + + /** + * @param name + * the name of the new branch + * @return this instance + */ + public CreateBranchCommand setName(String name) { + checkCallable(); + this.name = name; + return this; + } + + /** + * @param force + * if true and the branch with the given name + * already exists, the start-point of an existing branch will be + * set to a new start-point; if false, the existing branch will + * not be changed + * @return this instance + */ + public CreateBranchCommand setForce(boolean force) { + checkCallable(); + this.force = force; + return this; + } + + /** + * @param startPoint + * corresponds to the start-point option; if null, + * the current HEAD will be used + * @return this instance + */ + public CreateBranchCommand setStartPoint(String startPoint) { + checkCallable(); + this.startPoint = startPoint; + this.startCommit = null; + return this; + } + + /** + * @param startPoint + * corresponds to the start-point option; if null, + * the current HEAD will be used + * @return this instance + */ + public CreateBranchCommand setStartPoint(RevCommit startPoint) { + checkCallable(); + this.startCommit = startPoint; + this.startPoint = null; + return this; + } + + /** + * @param mode + * corresponds to the --track/--no-track/--set-upstream options; + * may be null + * @return this instance + */ + public CreateBranchCommand setUpstreamMode(SetupUpstreamMode mode) { + checkCallable(); + this.upstreamMode = mode; + return this; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java new file mode 100644 index 000000000..c0d95f3f5 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DeleteBranchCommand.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2010, Mathias Kinzler + * Copyright (C) 2010, Chris Aniszczyk + * 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.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.api.errors.CannotDeleteCurrentBranchException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.api.errors.NotMergedException; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; + +/** + * Used to delete one or several branches. + * + * The result of {@link #call()} is a list with the (full) names of the deleted + * branches. + * + * Note that we don't have a setter corresponding to the -r option; remote + * tracking branches are simply deleted just like local branches. + * + * @see Git documentation about Branch + */ +public class DeleteBranchCommand extends GitCommand> { + private final Set branchNames = new HashSet(); + + private boolean force; + + /** + * @param repo + */ + protected DeleteBranchCommand(Repository repo) { + super(repo); + } + + /** + * @throws NotMergedException + * when trying to delete a branch which has not been merged into + * the currently checked out branch without force + * @return the list with the (full) names of the deleted branches + */ + public List call() throws JGitInternalException, + NotMergedException, CannotDeleteCurrentBranchException { + checkCallable(); + List result = new ArrayList(); + if (branchNames.isEmpty()) + return result; + try { + String currentBranch = repo.getFullBranch(); + if (!force) { + // check if the branches to be deleted + // are all merged into the current branch + RevWalk walk = new RevWalk(repo); + RevCommit tip = walk.parseCommit(repo.resolve(Constants.HEAD)); + for (String branchName : branchNames) { + if (branchName == null) + continue; + Ref currentRef = repo.getRef(branchName); + if (currentRef == null) + continue; + + RevCommit base = walk.parseCommit(repo.resolve(branchName)); + if (!walk.isMergedInto(base, tip)) { + throw new NotMergedException(); + } + } + } + setCallable(false); + for (String branchName : branchNames) { + if (branchName == null) + continue; + Ref currentRef = repo.getRef(branchName); + if (currentRef == null) + continue; + if (currentRef.getName().equals(currentBranch)) + throw new CannotDeleteCurrentBranchException( + MessageFormat + .format( + JGitText.get().cannotDeleteCheckedOutBranch, + branchName)); + RefUpdate update = repo.updateRef(currentRef.getName()); + update.setRefLogMessage("branch deleted", false); + update.setForceUpdate(true); + Result deleteResult = update.delete(); + + boolean ok = true; + switch (deleteResult) { + case IO_FAILURE: + case LOCK_FAILURE: + case REJECTED: + ok = false; + break; + default: + break; + } + + if (ok) { + result.add(currentRef.getName()); + // remove upstream configuration if any + repo.getConfig().unsetSection( + ConfigConstants.CONFIG_BRANCH_SECTION, branchName); + repo.getConfig().save(); + } else + throw new JGitInternalException(MessageFormat.format( + JGitText.get().deleteBranchUnexpectedResult, + deleteResult.name())); + } + return result; + } catch (IOException ioe) { + throw new JGitInternalException(ioe.getMessage(), ioe); + } + } + + /** + * @param branchnames + * the names of the branches to delete; if not set, this will do + * nothing; invalid branch names will simply be ignored + * @return this instance + */ + public DeleteBranchCommand setBranchNames(String... branchnames) { + checkCallable(); + this.branchNames.clear(); + for (String branch : branchnames) + this.branchNames.add(branch); + return this; + } + + /** + * @param force + * true corresponds to the -D option, + * false to the -d option (default)
+ * if false a check will be performed whether the + * branch to be deleted is already merged into the current branch + * and deletion will be refused in this case + * @return this instance + */ + public DeleteBranchCommand setForce(boolean force) { + checkCallable(); + this.force = force; + return this; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java index 493019a15..eef5ab98d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java @@ -142,6 +142,42 @@ public class Git { return new PullCommand(repo); } + /** + * Returns a command object used to create branches + * + * @return a {@link CreateBranchCommand} + */ + public CreateBranchCommand branchCreate() { + return new CreateBranchCommand(repo); + } + + /** + * Returns a command object used to delete branches + * + * @return a {@link DeleteBranchCommand} + */ + public DeleteBranchCommand branchDelete() { + return new DeleteBranchCommand(repo); + } + + /** + * Returns a command object used to list branches + * + * @return a {@link ListBranchCommand} + */ + public ListBranchCommand branchList() { + return new ListBranchCommand(repo); + } + + /** + * Returns a command object used to rename branches + * + * @return a {@link RenameBranchCommand} + */ + public RenameBranchCommand branchRename() { + return new RenameBranchCommand(repo); + } + /** * Returns a command object to execute a {@code Add} command * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java new file mode 100644 index 000000000..8329fca56 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ListBranchCommand.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2010, Mathias Kinzler + * Copyright (C) 2010, Chris Aniszczyk + * 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.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; + +/** + * Used to obtain a list of branches. + * + * @see Git documentation about Branch + */ +public class ListBranchCommand extends GitCommand> { + private ListMode listMode; + + /** + * The modes available for listing branches (corresponding to the -r and -a + * options) + */ + public enum ListMode { + /** + * Corresponds to the -a option (all branches) + */ + ALL, + /** + * Corresponds to the -r option (remote branches only) + */ + REMOTE; + } + + /** + * @param repo + */ + protected ListBranchCommand(Repository repo) { + super(repo); + } + + /** + * @throws JGitInternalException + * upon internal failure + */ + public List call() throws JGitInternalException { + checkCallable(); + Map refList; + try { + if (listMode == null) { + refList = repo.getRefDatabase().getRefs(Constants.R_HEADS); + } else if (listMode == ListMode.REMOTE) { + refList = repo.getRefDatabase().getRefs(Constants.R_REMOTES); + } else { + refList = repo.getRefDatabase().getRefs(Constants.R_HEADS); + refList.putAll(repo.getRefDatabase().getRefs( + Constants.R_REMOTES)); + } + } catch (IOException e) { + throw new JGitInternalException(e.getMessage(), e); + } + List resultRefs = new ArrayList(); + resultRefs.addAll(refList.values()); + Collections.sort(resultRefs, new Comparator() { + public int compare(Ref o1, Ref o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + setCallable(false); + return resultRefs; + } + + /** + * @param listMode + * optional: corresponds to the -r/-a options; by default, only + * local branches will be listed + * @return this instance + */ + public ListBranchCommand setListMode(ListMode listMode) { + checkCallable(); + this.listMode = listMode; + return this; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java new file mode 100644 index 000000000..465498132 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2010, Mathias Kinzler + * Copyright (C) 2010, Chris Aniszczyk + * 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.IOException; +import java.text.MessageFormat; + +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.api.errors.DetachedHeadException; +import org.eclipse.jgit.api.errors.InvalidRefNameException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.api.errors.RefAlreadyExistsException; +import org.eclipse.jgit.api.errors.RefNotFoundException; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefRename; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RefUpdate.Result; + +/** + * Used to rename branches. + * + * @see Git documentation about Branch + */ +public class RenameBranchCommand extends GitCommand { + private String oldName; + + private String newName; + + /** + * @param repo + */ + protected RenameBranchCommand(Repository repo) { + super(repo); + } + + /** + * @throws RefNotFoundException + * if the old branch can not be found (branch with provided old + * name does not exist or old name resolves to a tag) + * @throws InvalidRefNameException + * if the provided new name is null or otherwise + * invalid + * @throws RefAlreadyExistsException + * if a branch with the new name already exists + * @throws DetachedHeadException + * if rename is tried without specifying the old name and HEAD + * is detached + */ + public Ref call() throws RefNotFoundException, InvalidRefNameException, + RefAlreadyExistsException, DetachedHeadException { + checkCallable(); + + if (newName == null) + throw new InvalidRefNameException(MessageFormat.format(JGitText + .get().branchNameInvalid, "")); + + try { + String fullOldName; + String fullNewName; + if (repo.getRef(newName) != null) + throw new RefAlreadyExistsException(MessageFormat.format( + JGitText.get().refAlreadExists, newName)); + if (oldName != null) { + Ref ref = repo.getRef(oldName); + if (ref == null) + throw new RefNotFoundException(MessageFormat.format( + JGitText.get().refNotResolved, oldName)); + if (ref.getName().startsWith(Constants.R_TAGS)) + throw new RefNotFoundException(MessageFormat.format( + JGitText.get().renameBranchFailedBecauseTag, + oldName)); + fullOldName = ref.getName(); + } else { + fullOldName = repo.getFullBranch(); + if (ObjectId.isId(fullOldName)) + throw new DetachedHeadException(); + } + + if (fullOldName.startsWith(Constants.R_REMOTES)) + fullNewName = Constants.R_REMOTES + newName; + else { + fullNewName = Constants.R_HEADS + newName; + } + + if (!Repository.isValidRefName(fullNewName)) + throw new InvalidRefNameException(MessageFormat.format(JGitText + .get().branchNameInvalid, fullNewName)); + + RefRename rename = repo.renameRef(fullOldName, fullNewName); + Result renameResult = rename.rename(); + + setCallable(false); + + boolean ok = Result.RENAMED == renameResult; + + if (ok) { + if (fullNewName.startsWith(Constants.R_HEADS)) { + // move the upstream configuration over to the new branch + String shortOldName = fullOldName + .substring(Constants.R_HEADS.length()); + String oldRemote = repo.getConfig().getString( + ConfigConstants.CONFIG_BRANCH_SECTION, + shortOldName, ConfigConstants.CONFIG_KEY_REMOTE); + if (oldRemote != null) { + repo.getConfig().setString( + ConfigConstants.CONFIG_BRANCH_SECTION, newName, + ConfigConstants.CONFIG_KEY_REMOTE, oldRemote); + } + String oldMerge = repo.getConfig().getString( + ConfigConstants.CONFIG_BRANCH_SECTION, + shortOldName, ConfigConstants.CONFIG_KEY_MERGE); + if (oldMerge != null) { + repo.getConfig().setString( + ConfigConstants.CONFIG_BRANCH_SECTION, newName, + ConfigConstants.CONFIG_KEY_MERGE, oldMerge); + } + repo.getConfig() + .unsetSection( + ConfigConstants.CONFIG_BRANCH_SECTION, + shortOldName); + repo.getConfig().save(); + } + + } else + throw new JGitInternalException(MessageFormat.format(JGitText + .get().renameBranchUnexpectedResult, renameResult + .name())); + + Ref resultRef = repo.getRef(newName); + if (resultRef == null) + throw new JGitInternalException( + JGitText.get().renameBranchFailedUnknownReason); + return resultRef; + } catch (IOException ioe) { + throw new JGitInternalException(ioe.getMessage(), ioe); + } + } + + /** + * @param newName + * the new name + * @return this instance + */ + public RenameBranchCommand setNewName(String newName) { + checkCallable(); + this.newName = newName; + return this; + } + + /** + * @param oldName + * the name of the branch to rename; if not set, the currently + * checked out branch (if any) will be renamed + * @return this instance + */ + public RenameBranchCommand setOldName(String oldName) { + checkCallable(); + this.oldName = oldName; + return this; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CannotDeleteCurrentBranchException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CannotDeleteCurrentBranchException.java new file mode 100644 index 000000000..76d773229 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/CannotDeleteCurrentBranchException.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2010,Mathias Kinzler 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.errors; + +/** + * Thrown when trying to delete a branch which is currently checked out + */ +public class CannotDeleteCurrentBranchException extends GitAPIException { + private static final long serialVersionUID = 1L; + + /** + * @param message + * the message + */ + public CannotDeleteCurrentBranchException(String message) { + super(message); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidRefNameException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidRefNameException.java new file mode 100644 index 000000000..139c41cd5 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/InvalidRefNameException.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010, Mathias Kinzler 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.errors; + +/** + * Exception thrown when an invalid Ref name was encountered + */ +public class InvalidRefNameException extends GitAPIException { + private static final long serialVersionUID = 1L; + + /** + * @param msg + */ + public InvalidRefNameException(String msg) { + super(msg); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/NotMergedException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/NotMergedException.java new file mode 100644 index 000000000..917c6355f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/NotMergedException.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010,Mathias Kinzler 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.errors; + +import org.eclipse.jgit.JGitText; + +/** + * Thrown when branch deletion fails due to unmerged data + */ +public class NotMergedException extends GitAPIException { + private static final long serialVersionUID = 1L; + + /** + * The default constructor with a default message + */ + public NotMergedException() { + super(JGitText.get().notMergedExceptionMessage); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefAlreadyExistsException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefAlreadyExistsException.java new file mode 100644 index 000000000..5e3ebf8d8 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefAlreadyExistsException.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2010,Mathias Kinzler 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.errors; + +import org.eclipse.jgit.lib.Ref; + +/** + * Thrown when trying to create a {@link Ref} with the same name as an exsiting + * one + */ +public class RefAlreadyExistsException extends GitAPIException { + private static final long serialVersionUID = 1L; + + /** + * @param message + */ + public RefAlreadyExistsException(String message) { + super(message); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefNotFoundException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefNotFoundException.java new file mode 100644 index 000000000..c8d96a026 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/RefNotFoundException.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010,Mathias Kinzler 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.errors; + +/** + * Thrown when a Ref can not be resolved + */ +public class RefNotFoundException extends GitAPIException { + private static final long serialVersionUID = 1L; + + /** + * @param message + */ + public RefNotFoundException(String message) { + super(message); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java index e63f4e9d0..4ca11d0fa 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java @@ -87,4 +87,6 @@ public class ConfigConstants { /** The "url" key */ public static final String CONFIG_KEY_URL = "url"; + /** The "autosetupmerge" key */ + public static final String CONFIG_KEY_AUTOSETUPMERGE = "autosetupmerge"; }