jgit clone --branch foo <url> did not fail if the remote branch "foo" didn't exist in the remote repository being cloned. Bug: 546580 Change-Id: I55648ad3a39da4a5711dfa8e6d6682bb8190a6d6 Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>tags/v5.11.0.202102240950-m3
@@ -11,7 +11,9 @@ package org.eclipse.jgit.pgm; | |||
import static org.junit.Assert.assertArrayEquals; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertNotEquals; | |||
import static org.junit.Assert.assertNotNull; | |||
import static org.junit.Assert.assertThrows; | |||
import static org.junit.Assert.assertTrue; | |||
import java.io.File; | |||
@@ -25,6 +27,7 @@ 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.revwalk.RevCommit; | |||
import org.eclipse.jgit.transport.RefSpec; | |||
@@ -64,6 +67,45 @@ public class CloneTest extends CLIRepositoryTestCase { | |||
assertEquals("expected 1 branch", 1, branches.size()); | |||
} | |||
@Test | |||
public void testCloneInitialBranch() throws Exception { | |||
createInitialCommit(); | |||
File gitDir = db.getDirectory(); | |||
String sourceURI = gitDir.toURI().toString(); | |||
File target = createTempDirectory("target"); | |||
String cmd = "git clone --branch master " + sourceURI + " " | |||
+ shellQuote(target.getPath()); | |||
String[] result = execute(cmd); | |||
assertArrayEquals(new String[] { | |||
"Cloning into '" + target.getPath() + "'...", "", "" }, result); | |||
Git git2 = Git.open(target); | |||
List<Ref> branches = git2.branchList().call(); | |||
assertEquals("expected 1 branch", 1, branches.size()); | |||
Repository db2 = git2.getRepository(); | |||
ObjectId head = db2.resolve("HEAD"); | |||
assertNotNull(head); | |||
assertNotEquals(ObjectId.zeroId(), head); | |||
ObjectId master = db2.resolve("master"); | |||
assertEquals(head, master); | |||
} | |||
@Test | |||
public void testCloneInitialBranchMissing() throws Exception { | |||
createInitialCommit(); | |||
File gitDir = db.getDirectory(); | |||
String sourceURI = gitDir.toURI().toString(); | |||
File target = createTempDirectory("target"); | |||
String cmd = "git clone --branch foo " + sourceURI + " " | |||
+ shellQuote(target.getPath()); | |||
Die e = assertThrows(Die.class, () -> execute(cmd)); | |||
assertEquals("Remote branch 'foo' not found in upstream origin", | |||
e.getMessage()); | |||
} | |||
private RevCommit createInitialCommit() throws Exception { | |||
JGitTestUtil.writeTrashFile(db, "hello.txt", "world"); | |||
git.add().addFilepattern("hello.txt").call(); |
@@ -18,6 +18,7 @@ import java.util.Collection; | |||
import org.eclipse.jgit.api.CloneCommand; | |||
import org.eclipse.jgit.api.Git; | |||
import org.eclipse.jgit.api.errors.InvalidRemoteException; | |||
import org.eclipse.jgit.api.errors.TransportException; | |||
import org.eclipse.jgit.lib.AnyObjectId; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.TextProgressMonitor; | |||
@@ -110,6 +111,8 @@ class Clone extends AbstractFetchCommand implements CloneCommand.Callback { | |||
db = command.call().getRepository(); | |||
if (msgs && db.resolve(Constants.HEAD) == null) | |||
outw.println(CLIText.get().clonedEmptyRepository); | |||
} catch (TransportException e) { | |||
throw die(e.getMessage(), e); | |||
} catch (InvalidRemoteException e) { | |||
throw die(MessageFormat.format(CLIText.get().doesNotExist, | |||
sourceUri), e); |
@@ -568,6 +568,7 @@ refNotResolved=Ref {0} cannot be resolved | |||
reftableDirExists=reftable dir exists and is nonempty | |||
reftableRecordsMustIncrease=records must be increasing: last {0}, this {1} | |||
refUpdateReturnCodeWas=RefUpdate return code was: {0} | |||
remoteBranchNotFound=Remote branch ''{0}'' not found in upstream origin | |||
remoteConfigHasNoURIAssociated=Remote config "{0}" has no URIs associated | |||
remoteDoesNotHaveSpec=Remote does not have {0} available for fetch. | |||
remoteDoesNotSupportSmartHTTPPush=remote does not support smart HTTP push |
@@ -297,6 +297,7 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { | |||
command.setTagOpt( | |||
fetchAll ? TagOpt.FETCH_TAGS : TagOpt.AUTO_FOLLOW); | |||
} | |||
command.setInitialBranch(branch); | |||
configure(command); | |||
return command.call(); |
@@ -74,6 +74,8 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> { | |||
private boolean isForceUpdate; | |||
private String initialBranch; | |||
/** | |||
* Callback for status of fetch operation. | |||
* | |||
@@ -209,7 +211,7 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> { | |||
transport.setFetchThin(thin); | |||
configure(transport); | |||
FetchResult result = transport.fetch(monitor, | |||
applyOptions(refSpecs)); | |||
applyOptions(refSpecs), initialBranch); | |||
if (!repo.isBare()) { | |||
fetchSubmodules(result); | |||
} | |||
@@ -487,6 +489,24 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> { | |||
return this; | |||
} | |||
/** | |||
* Set the initial branch | |||
* | |||
* @param branch | |||
* the initial branch to check out when cloning the repository. | |||
* Can be specified as ref name (<code>refs/heads/master</code>), | |||
* branch name (<code>master</code>) or tag name | |||
* (<code>v1.2.3</code>). The default is to use the branch | |||
* pointed to by the cloned repository's HEAD and can be | |||
* requested by passing {@code null} or <code>HEAD</code>. | |||
* @return {@code this} | |||
* @since 5.11 | |||
*/ | |||
public FetchCommand setInitialBranch(String branch) { | |||
this.initialBranch = branch; | |||
return this; | |||
} | |||
/** | |||
* Register a progress callback. | |||
* |
@@ -596,6 +596,7 @@ public class JGitText extends TranslationBundle { | |||
/***/ public String reftableDirExists; | |||
/***/ public String reftableRecordsMustIncrease; | |||
/***/ public String refUpdateReturnCodeWas; | |||
/***/ public String remoteBranchNotFound; | |||
/***/ public String remoteConfigHasNoURIAssociated; | |||
/***/ public String remoteDoesNotHaveSpec; | |||
/***/ public String remoteDoesNotSupportSmartHTTPPush; |
@@ -48,6 +48,7 @@ import org.eclipse.jgit.lib.Ref; | |||
import org.eclipse.jgit.lib.RefDatabase; | |||
import org.eclipse.jgit.revwalk.ObjectWalk; | |||
import org.eclipse.jgit.revwalk.RevWalk; | |||
import org.eclipse.jgit.util.StringUtils; | |||
class FetchProcess { | |||
/** Transport we will fetch over. */ | |||
@@ -79,7 +80,8 @@ class FetchProcess { | |||
toFetch = f; | |||
} | |||
void execute(ProgressMonitor monitor, FetchResult result) | |||
void execute(ProgressMonitor monitor, FetchResult result, | |||
String initialBranch) | |||
throws NotSupportedException, TransportException { | |||
askFor.clear(); | |||
localUpdates.clear(); | |||
@@ -89,7 +91,7 @@ class FetchProcess { | |||
Throwable e1 = null; | |||
try { | |||
executeImp(monitor, result); | |||
executeImp(monitor, result, initialBranch); | |||
} catch (NotSupportedException | TransportException err) { | |||
e1 = err; | |||
throw err; | |||
@@ -107,9 +109,22 @@ class FetchProcess { | |||
} | |||
} | |||
private boolean isInitialBranchMissing(Map<String, Ref> refsMap, | |||
String initialBranch) { | |||
if (StringUtils.isEmptyOrNull(initialBranch) || refsMap.isEmpty()) { | |||
return false; | |||
} | |||
if (refsMap.containsKey(initialBranch) | |||
|| refsMap.containsKey(Constants.R_HEADS + initialBranch) | |||
|| refsMap.containsKey(Constants.R_TAGS + initialBranch)) { | |||
return false; | |||
} | |||
return true; | |||
} | |||
private void executeImp(final ProgressMonitor monitor, | |||
final FetchResult result) throws NotSupportedException, | |||
TransportException { | |||
final FetchResult result, String initialBranch) | |||
throws NotSupportedException, TransportException { | |||
final TagOpt tagopt = transport.getTagOpt(); | |||
String getTags = (tagopt == TagOpt.NO_TAGS) ? null : Constants.R_TAGS; | |||
String getHead = null; | |||
@@ -126,7 +141,12 @@ class FetchProcess { | |||
} | |||
conn = transport.openFetch(toFetch, getTags, getHead); | |||
try { | |||
result.setAdvertisedRefs(transport.getURI(), conn.getRefsMap()); | |||
Map<String, Ref> refsMap = conn.getRefsMap(); | |||
if (isInitialBranchMissing(refsMap, initialBranch)) { | |||
throw new TransportException(MessageFormat.format( | |||
JGitText.get().remoteBranchNotFound, initialBranch)); | |||
} | |||
result.setAdvertisedRefs(transport.getURI(), refsMap); | |||
result.peerUserAgent = conn.getPeerUserAgent(); | |||
final Set<Ref> matched = new HashSet<>(); | |||
for (RefSpec spec : toFetch) { |
@@ -1231,9 +1231,52 @@ public abstract class Transport implements AutoCloseable { | |||
* the remote connection could not be established or object | |||
* copying (if necessary) failed or update specification was | |||
* incorrect. | |||
* @since 5.11 | |||
*/ | |||
public FetchResult fetch(final ProgressMonitor monitor, | |||
Collection<RefSpec> toFetch) | |||
throws NotSupportedException, TransportException { | |||
return fetch(monitor, toFetch, null); | |||
} | |||
/** | |||
* Fetch objects and refs from the remote repository to the local one. | |||
* <p> | |||
* This is a utility function providing standard fetch behavior. Local | |||
* tracking refs associated with the remote repository are automatically | |||
* updated if this transport was created from a | |||
* {@link org.eclipse.jgit.transport.RemoteConfig} with fetch RefSpecs | |||
* defined. | |||
* | |||
* @param monitor | |||
* progress monitor to inform the user about our processing | |||
* activity. Must not be null. Use | |||
* {@link org.eclipse.jgit.lib.NullProgressMonitor} if progress | |||
* updates are not interesting or necessary. | |||
* @param toFetch | |||
* specification of refs to fetch locally. May be null or the | |||
* empty collection to use the specifications from the | |||
* RemoteConfig. Source for each RefSpec can't be null. | |||
* @param branch | |||
* the initial branch to check out when cloning the repository. | |||
* Can be specified as ref name (<code>refs/heads/master</code>), | |||
* branch name (<code>master</code>) or tag name | |||
* (<code>v1.2.3</code>). The default is to use the branch | |||
* pointed to by the cloned repository's HEAD and can be | |||
* requested by passing {@code null} or <code>HEAD</code>. | |||
* @return information describing the tracking refs updated. | |||
* @throws org.eclipse.jgit.errors.NotSupportedException | |||
* this transport implementation does not support fetching | |||
* objects. | |||
* @throws org.eclipse.jgit.errors.TransportException | |||
* the remote connection could not be established or object | |||
* copying (if necessary) failed or update specification was | |||
* incorrect. | |||
* @since 5.11 | |||
*/ | |||
public FetchResult fetch(final ProgressMonitor monitor, | |||
Collection<RefSpec> toFetch) throws NotSupportedException, | |||
Collection<RefSpec> toFetch, String branch) | |||
throws NotSupportedException, | |||
TransportException { | |||
if (toFetch == null || toFetch.isEmpty()) { | |||
// If the caller did not ask for anything use the defaults. | |||
@@ -1263,7 +1306,7 @@ public abstract class Transport implements AutoCloseable { | |||
} | |||
final FetchResult result = new FetchResult(); | |||
new FetchProcess(this, toFetch).execute(monitor, result); | |||
new FetchProcess(this, toFetch).execute(monitor, result, branch); | |||
local.autoGC(monitor); | |||