]> source.dussan.org Git - jgit.git/commitdiff
PushCommand: consider push.default when no RefSpecs are given 29/136629/5
authorRolf Theunissen <rolf.theunissen@gmail.com>
Sun, 10 Feb 2019 17:40:26 +0000 (18:40 +0100)
committerMatthias Sohn <matthias.sohn@sap.com>
Mon, 14 Feb 2022 09:45:15 +0000 (10:45 +0100)
When no RefSpecs are given, PushCommand until now simply fell back to
pushing the current branch to an upstream branch of the same name. This
corresponds to push.default=current. Any setting from the git config
for push.default was simply ignored.

Implement the other modes (nothing, matching, upstream, and simple),
too. Add a setter and getter for the PushDefault so that an application
can force a particular mode to be used. For backwards compatibility,
use "current" as the default setting; to figure out the value from the
git config, which defaults to "simple", call setPushDefault(null).

Bug: 351314
Change-Id: I86c5402318771e47d80b137e99947762e1150bb4
Signed-off-by: Rolf Theunissen <rolf.theunissen@gmail.com>
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java

index 0a6e143c91ca1bed8baed79a97c54030e98023d1..3f8921c152c0e4c0d518644881ec91a235156d54 100644 (file)
@@ -11,6 +11,7 @@ package org.eclipse.jgit.api;
 
 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.io.IOException;
 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.RefUpdate;
 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 class PushCommandTest extends RepositoryTestCase {
                }
        }
 
+       /**
+        * 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
         *
index ee97c265e9b9d308de9402ce741107ecea5bfead..31579c98ad5618e2cbf8b1fae011e32adbc73490 100644 (file)
@@ -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}
index dc681bad06dc13f7c5baf7931189e93ae1d9b083..0deb7ed29c31af80b0227396a2a2623c5f5ab0dd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com> and others
+ * Copyright (C) 2010, 2022 Chris Aniszczyk <caniszczyk@gmail.com> 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.HashMap;
 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.TooLargeObjectInPackException;
 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<String> pushOptions;
 
+       // Legacy behavior as default. Use setPushDefault(null) to determine the
+       // value from the git config.
+       private PushDefault pushDefault = PushDefault.CURRENT;
+
        /**
         * <p>
         * Constructor for PushCommand.
@@ -103,15 +113,14 @@ public class PushCommand extends
                ArrayList<PushResult> 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 class PushCommand extends
                                        refSpecs.set(i, refSpecs.get(i).setForceUpdate(true));
                        }
 
-                       final List<Transport> transports;
-                       transports = Transport.openAll(repo, remote, Transport.Operation.PUSH);
+                       List<Transport> 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 class PushCommand extends
                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 <code>Constants.DEFAULT_REMOTE_NAME</code> will
@@ -336,10 +413,38 @@ public class PushCommand extends
                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 class PushCommand extends
        /**
         * Push all tags under refs/tags/*.
         *
-        * @return {code this}
+        * @return {@code this}
         */
        public PushCommand setPushTags() {
                refSpecs.add(Transport.REFSPEC_TAGS);
index f7ebe4f40fd780da35338cf64f1368f5b47a895e..58615b44d829be07419e762ddf553ec0cebc4e4e 100644 (file)
@@ -592,6 +592,11 @@ public class JGitText extends TranslationBundle {
        /***/ 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;