diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java index 0a6e143c9..3f8921c15 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java @@ -11,6 +11,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -19,7 +20,9 @@ import java.net.URISyntaxException; import java.util.Properties; +import org.eclipse.jgit.api.errors.DetachedHeadException; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.errors.MissingObjectException; @@ -32,6 +35,7 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.PushConfig.PushDefault; import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.RefLeaseSpec; import org.eclipse.jgit.transport.RefSpec; @@ -289,6 +293,565 @@ public void testPushWithoutPushRefSpec() throws Exception { } } + /** + * Check that pushing from a detached HEAD without refspec throws a + * DetachedHeadException. + * + * @throws Exception + */ + @Test + public void testPushDefaultDetachedHead() throws Exception { + try (Git git = new Git(db); + Git git2 = new Git(createBareRepository())) { + final StoredConfig config = git.getRepository().getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish( + git2.getRepository().getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.addFetchRefSpec( + new RefSpec("+refs/heads/*:refs/remotes/origin/*")); + remoteConfig.update(config); + config.save(); + + writeTrashFile("f", "content of f"); + git.add().addFilepattern("f").call(); + RevCommit commit = git.commit().setMessage("adding f").call(); + + git.checkout().setName("not-pushed").setCreateBranch(true).call(); + git.checkout().setName("branchtopush").setCreateBranch(true).call(); + git.checkout().setName(commit.getName()).call(); + String head = git.getRepository().getFullBranch(); + assertTrue(ObjectId.isId(head)); + assertEquals(commit.getName(), head); + assertThrows(DetachedHeadException.class, + () -> git.push().setRemote("test").call()); + } + } + + /** + * Check that push.default=nothing without refspec throws an + * InvalidRefNameException. + * + * @throws Exception + */ + @Test + public void testPushDefaultNothing() throws Exception { + try (Git git = new Git(db); + Git git2 = new Git(createBareRepository())) { + final StoredConfig config = git.getRepository().getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish( + git2.getRepository().getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.addFetchRefSpec( + new RefSpec("+refs/heads/*:refs/remotes/origin/*")); + remoteConfig.update(config); + config.save(); + + writeTrashFile("f", "content of f"); + git.add().addFilepattern("f").call(); + git.commit().setMessage("adding f").call(); + + git.checkout().setName("not-pushed").setCreateBranch(true).call(); + git.checkout().setName("branchtopush").setCreateBranch(true).call(); + + assertEquals(null, + git2.getRepository().resolve("refs/heads/branchtopush")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/not-pushed")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/master")); + assertThrows(InvalidRefNameException.class, + () -> git.push().setRemote("test") + .setPushDefault(PushDefault.NOTHING).call()); + } + } + + /** + * Check that push.default=matching without refspec pushes all matching + * branches. + * + * @throws Exception + */ + @Test + public void testPushDefaultMatching() throws Exception { + try (Git git = new Git(db); + Git git2 = new Git(createBareRepository())) { + final StoredConfig config = git.getRepository().getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish( + git2.getRepository().getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.addFetchRefSpec( + new RefSpec("+refs/heads/*:refs/remotes/origin/*")); + remoteConfig.update(config); + config.save(); + + writeTrashFile("f", "content of f"); + git.add().addFilepattern("f").call(); + RevCommit commit = git.commit().setMessage("adding f").call(); + + git.checkout().setName("also-pushed").setCreateBranch(true).call(); + git.checkout().setName("branchtopush").setCreateBranch(true).call(); + + assertEquals(null, + git2.getRepository().resolve("refs/heads/branchtopush")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/also-pushed")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/master")); + git.push().setRemote("test").setPushDefault(PushDefault.MATCHING) + .call(); + assertEquals(commit.getId(), + git2.getRepository().resolve("refs/heads/branchtopush")); + assertEquals(commit.getId(), + git2.getRepository().resolve("refs/heads/also-pushed")); + assertEquals(commit.getId(), + git2.getRepository().resolve("refs/heads/master")); + assertEquals(commit.getId(), git.getRepository() + .resolve("refs/remotes/origin/branchtopush")); + assertEquals(commit.getId(), git.getRepository() + .resolve("refs/remotes/origin/also-pushed")); + assertEquals(commit.getId(), + git.getRepository().resolve("refs/remotes/origin/master")); + } + } + + /** + * Check that push.default=upstream without refspec pushes only the current + * branch to the configured upstream. + * + * @throws Exception + */ + @Test + public void testPushDefaultUpstream() throws Exception { + try (Git git = new Git(db); + Git git2 = new Git(createBareRepository())) { + StoredConfig config = git.getRepository().getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish( + git2.getRepository().getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.addFetchRefSpec( + new RefSpec("+refs/heads/*:refs/remotes/origin/*")); + remoteConfig.update(config); + config.save(); + + writeTrashFile("f", "content of f"); + git.add().addFilepattern("f").call(); + RevCommit commit = git.commit().setMessage("adding f").call(); + + git.checkout().setName("not-pushed").setCreateBranch(true).call(); + git.checkout().setName("branchtopush").setCreateBranch(true).call(); + + config = git.getRepository().getConfig(); + config.setString("branch", "branchtopush", "remote", "test"); + config.setString("branch", "branchtopush", "merge", + "refs/heads/upstreambranch"); + config.save(); + + assertEquals(null, + git2.getRepository().resolve("refs/heads/branchtopush")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/upstreambranch")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/not-pushed")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/master")); + git.push().setRemote("test").setPushDefault(PushDefault.UPSTREAM) + .call(); + assertEquals(null, + git2.getRepository().resolve("refs/heads/branchtopush")); + assertEquals(commit.getId(), + git2.getRepository().resolve("refs/heads/upstreambranch")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/not-pushed")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/master")); + assertEquals(commit.getId(), git.getRepository() + .resolve("refs/remotes/origin/upstreambranch")); + assertEquals(null, git.getRepository() + .resolve("refs/remotes/origin/branchtopush")); + } + } + + /** + * Check that push.default=upstream without refspec throws an + * InvalidRefNameException if the current branch has no upstream. + * + * @throws Exception + */ + @Test + public void testPushDefaultUpstreamNoTracking() throws Exception { + try (Git git = new Git(db); + Git git2 = new Git(createBareRepository())) { + StoredConfig config = git.getRepository().getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish( + git2.getRepository().getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.addFetchRefSpec( + new RefSpec("+refs/heads/*:refs/remotes/origin/*")); + remoteConfig.update(config); + config.save(); + + writeTrashFile("f", "content of f"); + git.add().addFilepattern("f").call(); + git.commit().setMessage("adding f").call(); + + git.checkout().setName("not-pushed").setCreateBranch(true).call(); + git.checkout().setName("branchtopush").setCreateBranch(true).call(); + + config = git.getRepository().getConfig(); + config.setString("branch", "branchtopush", "remote", "test"); + config.save(); + + assertThrows(InvalidRefNameException.class, + () -> git.push().setRemote("test") + .setPushDefault(PushDefault.UPSTREAM).call()); + } + } + + /** + * Check that push.default=upstream without refspec throws an + * InvalidRefNameException if the push remote is not the same as the fetch + * remote. + * + * @throws Exception + */ + @Test + public void testPushDefaultUpstreamTriangular() throws Exception { + try (Git git = new Git(db); + Git git2 = new Git(createBareRepository())) { + StoredConfig config = git.getRepository().getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish( + git2.getRepository().getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.addFetchRefSpec( + new RefSpec("+refs/heads/*:refs/remotes/origin/*")); + remoteConfig.update(config); + config.save(); + + writeTrashFile("f", "content of f"); + git.add().addFilepattern("f").call(); + git.commit().setMessage("adding f").call(); + + git.checkout().setName("not-pushed").setCreateBranch(true).call(); + git.checkout().setName("branchtopush").setCreateBranch(true).call(); + + config = git.getRepository().getConfig(); + // Don't configure a remote; it'll default to "origin". + config.setString("branch", "branchtopush", "merge", + "upstreambranch"); + config.save(); + + assertThrows(InvalidRefNameException.class, + () -> git.push().setRemote("test") + .setPushDefault(PushDefault.UPSTREAM).call()); + } + } + + /** + * Check that push.default=simple without refspec pushes only the current + * branch to the configured upstream name. + * + * @throws Exception + */ + @Test + public void testPushDefaultSimple() throws Exception { + try (Git git = new Git(db); + Git git2 = new Git(createBareRepository())) { + StoredConfig config = git.getRepository().getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish( + git2.getRepository().getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.addFetchRefSpec( + new RefSpec("+refs/heads/*:refs/remotes/origin/*")); + remoteConfig.update(config); + config.save(); + + writeTrashFile("f", "content of f"); + git.add().addFilepattern("f").call(); + RevCommit commit = git.commit().setMessage("adding f").call(); + + git.checkout().setName("not-pushed").setCreateBranch(true).call(); + git.checkout().setName("branchtopush").setCreateBranch(true).call(); + + config = git.getRepository().getConfig(); + config.setString("branch", "branchtopush", "remote", "test"); + config.setString("branch", "branchtopush", "merge", + "refs/heads/branchtopush"); + config.save(); + + assertEquals(null, + git2.getRepository().resolve("refs/heads/branchtopush")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/not-pushed")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/master")); + git.push().setRemote("test").setPushDefault(PushDefault.SIMPLE) + .call(); + assertEquals(commit.getId(), + git2.getRepository().resolve("refs/heads/branchtopush")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/not-pushed")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/master")); + assertEquals(commit.getId(), git.getRepository() + .resolve("refs/remotes/origin/branchtopush")); + } + } + + /** + * Check that push.default=simple without refspec pushes only the current + * branch to a branch with the same name in a triangular workflow. + * + * @throws Exception + */ + @Test + public void testPushDefaultSimpleTriangular() throws Exception { + try (Git git = new Git(db); + Git git2 = new Git(createBareRepository())) { + StoredConfig config = git.getRepository().getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish( + git2.getRepository().getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.addFetchRefSpec( + new RefSpec("+refs/heads/*:refs/remotes/origin/*")); + remoteConfig.update(config); + config.save(); + + writeTrashFile("f", "content of f"); + git.add().addFilepattern("f").call(); + RevCommit commit = git.commit().setMessage("adding f").call(); + + git.checkout().setName("not-pushed").setCreateBranch(true).call(); + git.checkout().setName("branchtopush").setCreateBranch(true).call(); + + config = git.getRepository().getConfig(); + // Don't set remote, it'll default to "origin". Configure a + // different + // branch name; should be ignored. + config.setString("branch", "branchtopush", "merge", + "refs/heads/upstreambranch"); + config.save(); + + assertEquals(null, + git2.getRepository().resolve("refs/heads/branchtopush")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/upstreambranch")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/not-pushed")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/master")); + git.push().setRemote("test").setPushDefault(PushDefault.SIMPLE) + .call(); + assertEquals(commit.getId(), + git2.getRepository().resolve("refs/heads/branchtopush")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/upstreambranch")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/not-pushed")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/master")); + assertEquals(commit.getId(), git.getRepository() + .resolve("refs/remotes/origin/branchtopush")); + } + } + + /** + * Check that push.default=simple without refspec throws an + * InvalidRefNameException if the current branch has no upstream. + * + * @throws Exception + */ + @Test + public void testPushDefaultSimpleNoTracking() throws Exception { + try (Git git = new Git(db); + Git git2 = new Git(createBareRepository())) { + StoredConfig config = git.getRepository().getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish( + git2.getRepository().getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.addFetchRefSpec( + new RefSpec("+refs/heads/*:refs/remotes/origin/*")); + remoteConfig.update(config); + config.save(); + + writeTrashFile("f", "content of f"); + git.add().addFilepattern("f").call(); + git.commit().setMessage("adding f").call(); + + git.checkout().setName("not-pushed").setCreateBranch(true).call(); + git.checkout().setName("branchtopush").setCreateBranch(true).call(); + + config = git.getRepository().getConfig(); + config.setString("branch", "branchtopush", "remote", "test"); + config.save(); + + assertThrows(InvalidRefNameException.class, + () -> git.push().setRemote("test") + .setPushDefault(PushDefault.SIMPLE).call()); + } + } + + /** + * Check that push.default=simple without refspec throws an + * InvalidRefNameException if the current branch has an upstream with a + * different name. + * + * @throws Exception + */ + @Test + public void testPushDefaultSimpleDifferentTracking() throws Exception { + try (Git git = new Git(db); + Git git2 = new Git(createBareRepository())) { + StoredConfig config = git.getRepository().getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish( + git2.getRepository().getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.addFetchRefSpec( + new RefSpec("+refs/heads/*:refs/remotes/origin/*")); + remoteConfig.update(config); + config.save(); + + writeTrashFile("f", "content of f"); + git.add().addFilepattern("f").call(); + git.commit().setMessage("adding f").call(); + + git.checkout().setName("not-pushed").setCreateBranch(true).call(); + git.checkout().setName("branchtopush").setCreateBranch(true).call(); + + config = git.getRepository().getConfig(); + config.setString("branch", "branchtopush", "remote", "test"); + config.setString("branch", "branchtopush", "merge", + "refs/heads/upstreambranch"); + config.save(); + + assertThrows(InvalidRefNameException.class, + () -> git.push().setRemote("test") + .setPushDefault(PushDefault.SIMPLE).call()); + } + } + + /** + * Check that if no PushDefault is set, the value is read from the git + * config. + * + * @throws Exception + */ + @Test + public void testPushDefaultFromConfig() throws Exception { + try (Git git = new Git(db); + Git git2 = new Git(createBareRepository())) { + StoredConfig config = git.getRepository().getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish( + git2.getRepository().getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.addFetchRefSpec( + new RefSpec("+refs/heads/*:refs/remotes/origin/*")); + remoteConfig.update(config); + config.setString("push", null, "default", "upstream"); + config.save(); + + writeTrashFile("f", "content of f"); + git.add().addFilepattern("f").call(); + RevCommit commit = git.commit().setMessage("adding f").call(); + + git.checkout().setName("not-pushed").setCreateBranch(true).call(); + git.checkout().setName("branchtopush").setCreateBranch(true).call(); + + config = git.getRepository().getConfig(); + config.setString("branch", "branchtopush", "remote", "test"); + config.setString("branch", "branchtopush", "merge", + "refs/heads/upstreambranch"); + config.save(); + + assertEquals(null, + git2.getRepository().resolve("refs/heads/branchtopush")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/upstreambranch")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/not-pushed")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/master")); + PushCommand cmd = git.push(); + cmd.setRemote("test").setPushDefault(null).call(); + assertEquals(PushDefault.UPSTREAM, cmd.getPushDefault()); + assertEquals(null, + git2.getRepository().resolve("refs/heads/branchtopush")); + assertEquals(commit.getId(), + git2.getRepository().resolve("refs/heads/upstreambranch")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/not-pushed")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/master")); + assertEquals(commit.getId(), git.getRepository() + .resolve("refs/remotes/origin/upstreambranch")); + assertEquals(null, git.getRepository() + .resolve("refs/remotes/origin/branchtopush")); + } + } + + /** + * Check that if no PushDefault is set and none is set in the git config, it + * defaults to "simple". + * + * @throws Exception + */ + @Test + public void testPushDefaultFromConfigDefault() throws Exception { + try (Git git = new Git(db); + Git git2 = new Git(createBareRepository())) { + StoredConfig config = git.getRepository().getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, "test"); + URIish uri = new URIish( + git2.getRepository().getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + remoteConfig.addFetchRefSpec( + new RefSpec("+refs/heads/*:refs/remotes/origin/*")); + remoteConfig.update(config); + config.save(); + + writeTrashFile("f", "content of f"); + git.add().addFilepattern("f").call(); + RevCommit commit = git.commit().setMessage("adding f").call(); + + git.checkout().setName("not-pushed").setCreateBranch(true).call(); + git.checkout().setName("branchtopush").setCreateBranch(true).call(); + + config = git.getRepository().getConfig(); + config.setString("branch", "branchtopush", "remote", "test"); + config.setString("branch", "branchtopush", "merge", + "refs/heads/branchtopush"); + config.save(); + + assertEquals(null, + git2.getRepository().resolve("refs/heads/branchtopush")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/not-pushed")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/master")); + PushCommand cmd = git.push(); + cmd.setRemote("test").setPushDefault(null).call(); + assertEquals(PushDefault.SIMPLE, cmd.getPushDefault()); + assertEquals(commit.getId(), + git2.getRepository().resolve("refs/heads/branchtopush")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/not-pushed")); + assertEquals(null, + git2.getRepository().resolve("refs/heads/master")); + assertEquals(commit.getId(), git.getRepository() + .resolve("refs/remotes/origin/branchtopush")); + } + } + /** * Check that missing refs don't cause errors during push * diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index ee97c265e..31579c98a 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -564,6 +564,11 @@ pushCertificateInvalidField=Push certificate has missing or invalid value for {0 pushCertificateInvalidFieldValue=Push certificate has missing or invalid value for {0}: {1} pushCertificateInvalidHeader=Push certificate has invalid header format pushCertificateInvalidSignature=Push certificate has invalid signature format +pushDefaultNothing=No refspec given and push.default=nothing; no upstream branch can be determined +pushDefaultNoUpstream=No upstream branch found for local branch ''{0}'' +pushDefaultSimple=push.default=simple requires local branch name ''{0}'' to be equal to upstream tracked branch name ''{1}'' +pushDefaultTriangularUpstream=push.default=upstream cannot be used when the push remote ''{0}'' is different from the fetch remote ''{1}'' +pushDefaultUnknown=Unknown push.default={0}; cannot push pushIsNotSupportedForBundleTransport=Push is not supported for bundle transport pushNotPermitted=push not permitted pushOptionsNotSupported=Push options not supported; received {0} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java index dc681bad0..0deb7ed29 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010, Chris Aniszczyk and others + * Copyright (C) 2010, 2022 Chris Aniszczyk and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -21,7 +21,9 @@ import java.util.List; import java.util.Map; +import org.eclipse.jgit.api.errors.DetachedHeadException; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.InvalidRemoteException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.NotSupportedException; @@ -29,11 +31,15 @@ import org.eclipse.jgit.errors.TooLargePackException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.BranchConfig; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.PushConfig; +import org.eclipse.jgit.transport.PushConfig.PushDefault; import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.RefLeaseSpec; import org.eclipse.jgit.transport.RefSpec; @@ -71,6 +77,10 @@ public class PushCommand extends private List pushOptions; + // Legacy behavior as default. Use setPushDefault(null) to determine the + // value from the git config. + private PushDefault pushDefault = PushDefault.CURRENT; + /** *

* Constructor for PushCommand. @@ -103,15 +113,14 @@ public Iterable call() throws GitAPIException, ArrayList pushResults = new ArrayList<>(3); try { + Config config = repo.getConfig(); if (refSpecs.isEmpty()) { - RemoteConfig config = new RemoteConfig(repo.getConfig(), + RemoteConfig rc = new RemoteConfig(config, getRemote()); - refSpecs.addAll(config.getPushRefSpecs()); - } - if (refSpecs.isEmpty()) { - Ref head = repo.exactRef(Constants.HEAD); - if (head != null && head.isSymbolic()) - refSpecs.add(new RefSpec(head.getLeaf().getName())); + refSpecs.addAll(rc.getPushRefSpecs()); + if (refSpecs.isEmpty()) { + determineDefaultRefSpecs(config); + } } if (force) { @@ -119,8 +128,8 @@ public Iterable call() throws GitAPIException, refSpecs.set(i, refSpecs.get(i).setForceUpdate(true)); } - final List transports; - transports = Transport.openAll(repo, remote, Transport.Operation.PUSH); + List transports = Transport.openAll(repo, remote, + Transport.Operation.PUSH); for (@SuppressWarnings("resource") // Explicitly closed in finally final Transport transport : transports) { transport.setPushThin(thin); @@ -172,6 +181,74 @@ public Iterable call() throws GitAPIException, return pushResults; } + private String getCurrentBranch() + throws IOException, DetachedHeadException { + Ref head = repo.exactRef(Constants.HEAD); + if (head != null && head.isSymbolic()) { + return head.getLeaf().getName(); + } + throw new DetachedHeadException(); + } + + private void determineDefaultRefSpecs(Config config) + throws IOException, GitAPIException { + if (pushDefault == null) { + pushDefault = config.get(PushConfig::new).getPushDefault(); + } + switch (pushDefault) { + case CURRENT: + refSpecs.add(new RefSpec(getCurrentBranch())); + break; + case MATCHING: + setPushAll(); + break; + case NOTHING: + throw new InvalidRefNameException( + JGitText.get().pushDefaultNothing); + case SIMPLE: + case UPSTREAM: + String currentBranch = getCurrentBranch(); + BranchConfig branchCfg = new BranchConfig(config, + Repository.shortenRefName(currentBranch)); + String fetchRemote = branchCfg.getRemote(); + if (fetchRemote == null) { + fetchRemote = Constants.DEFAULT_REMOTE_NAME; + } + boolean isTriangular = !fetchRemote.equals(remote); + if (isTriangular) { + if (PushDefault.UPSTREAM.equals(pushDefault)) { + throw new InvalidRefNameException(MessageFormat.format( + JGitText.get().pushDefaultTriangularUpstream, + remote, fetchRemote)); + } + // Strange, but consistent with C git: "simple" doesn't even + // check whether there is a configured upstream, and if so, that + // it is equal to the local branch name. It just becomes + // "current". + refSpecs.add(new RefSpec(currentBranch)); + } else { + String trackedBranch = branchCfg.getMerge(); + if (branchCfg.isRemoteLocal() || trackedBranch == null + || !trackedBranch.startsWith(Constants.R_HEADS)) { + throw new InvalidRefNameException(MessageFormat.format( + JGitText.get().pushDefaultNoUpstream, + currentBranch)); + } + if (PushDefault.SIMPLE.equals(pushDefault) + && !trackedBranch.equals(currentBranch)) { + throw new InvalidRefNameException(MessageFormat.format( + JGitText.get().pushDefaultSimple, currentBranch, + trackedBranch)); + } + refSpecs.add(new RefSpec(currentBranch + ':' + trackedBranch)); + } + break; + default: + throw new InvalidRefNameException(MessageFormat + .format(JGitText.get().pushDefaultUnknown, pushDefault)); + } + } + /** * The remote (uri or name) used for the push operation. If no remote is * set, the default value of Constants.DEFAULT_REMOTE_NAME will @@ -336,10 +413,38 @@ public PushCommand setRefSpecs(List specs) { return this; } + /** + * Retrieves the {@link PushDefault} currently set. + * + * @return the {@link PushDefault}, or {@code null} if not set + * @since 6.1 + */ + public PushDefault getPushDefault() { + return pushDefault; + } + + /** + * Sets an explicit {@link PushDefault}. The default used if this is not + * called is {@link PushDefault#CURRENT} for compatibility reasons with + * earlier JGit versions. + * + * @param pushDefault + * {@link PushDefault} to set; if {@code null} the value defined + * in the git config will be used. + * + * @return {@code this} + * @since 6.1 + */ + public PushCommand setPushDefault(PushDefault pushDefault) { + checkCallable(); + this.pushDefault = pushDefault; + return this; + } + /** * Push all branches under refs/heads/*. * - * @return {code this} + * @return {@code this} */ public PushCommand setPushAll() { refSpecs.add(Transport.REFSPEC_PUSH_ALL); @@ -349,7 +454,7 @@ public PushCommand setPushAll() { /** * Push all tags under refs/tags/*. * - * @return {code this} + * @return {@code this} */ public PushCommand setPushTags() { refSpecs.add(Transport.REFSPEC_TAGS); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index f7ebe4f40..58615b44d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -592,6 +592,11 @@ public static JGitText get() { /***/ public String pushCertificateInvalidFieldValue; /***/ public String pushCertificateInvalidHeader; /***/ public String pushCertificateInvalidSignature; + /***/ public String pushDefaultNothing; + /***/ public String pushDefaultNoUpstream; + /***/ public String pushDefaultSimple; + /***/ public String pushDefaultTriangularUpstream; + /***/ public String pushDefaultUnknown; /***/ public String pushIsNotSupportedForBundleTransport; /***/ public String pushNotPermitted; /***/ public String pushOptionsNotSupported;