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
import static org.junit.Assert.assertArrayEquals; | import static org.junit.Assert.assertArrayEquals; | ||||
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
import static org.junit.Assert.assertNotEquals; | |||||
import static org.junit.Assert.assertNotNull; | import static org.junit.Assert.assertNotNull; | ||||
import static org.junit.Assert.assertThrows; | |||||
import static org.junit.Assert.assertTrue; | import static org.junit.Assert.assertTrue; | ||||
import java.io.File; | import java.io.File; | ||||
import org.eclipse.jgit.lib.ObjectId; | import org.eclipse.jgit.lib.ObjectId; | ||||
import org.eclipse.jgit.lib.Ref; | import org.eclipse.jgit.lib.Ref; | ||||
import org.eclipse.jgit.lib.RefUpdate; | import org.eclipse.jgit.lib.RefUpdate; | ||||
import org.eclipse.jgit.lib.Repository; | |||||
import org.eclipse.jgit.lib.StoredConfig; | import org.eclipse.jgit.lib.StoredConfig; | ||||
import org.eclipse.jgit.revwalk.RevCommit; | import org.eclipse.jgit.revwalk.RevCommit; | ||||
import org.eclipse.jgit.transport.RefSpec; | import org.eclipse.jgit.transport.RefSpec; | ||||
assertEquals("expected 1 branch", 1, branches.size()); | 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 { | private RevCommit createInitialCommit() throws Exception { | ||||
JGitTestUtil.writeTrashFile(db, "hello.txt", "world"); | JGitTestUtil.writeTrashFile(db, "hello.txt", "world"); | ||||
git.add().addFilepattern("hello.txt").call(); | git.add().addFilepattern("hello.txt").call(); |
import org.eclipse.jgit.api.CloneCommand; | import org.eclipse.jgit.api.CloneCommand; | ||||
import org.eclipse.jgit.api.Git; | import org.eclipse.jgit.api.Git; | ||||
import org.eclipse.jgit.api.errors.InvalidRemoteException; | 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.AnyObjectId; | ||||
import org.eclipse.jgit.lib.Constants; | import org.eclipse.jgit.lib.Constants; | ||||
import org.eclipse.jgit.lib.TextProgressMonitor; | import org.eclipse.jgit.lib.TextProgressMonitor; | ||||
db = command.call().getRepository(); | db = command.call().getRepository(); | ||||
if (msgs && db.resolve(Constants.HEAD) == null) | if (msgs && db.resolve(Constants.HEAD) == null) | ||||
outw.println(CLIText.get().clonedEmptyRepository); | outw.println(CLIText.get().clonedEmptyRepository); | ||||
} catch (TransportException e) { | |||||
throw die(e.getMessage(), e); | |||||
} catch (InvalidRemoteException e) { | } catch (InvalidRemoteException e) { | ||||
throw die(MessageFormat.format(CLIText.get().doesNotExist, | throw die(MessageFormat.format(CLIText.get().doesNotExist, | ||||
sourceUri), e); | sourceUri), e); |
reftableDirExists=reftable dir exists and is nonempty | reftableDirExists=reftable dir exists and is nonempty | ||||
reftableRecordsMustIncrease=records must be increasing: last {0}, this {1} | reftableRecordsMustIncrease=records must be increasing: last {0}, this {1} | ||||
refUpdateReturnCodeWas=RefUpdate return code was: {0} | refUpdateReturnCodeWas=RefUpdate return code was: {0} | ||||
remoteBranchNotFound=Remote branch ''{0}'' not found in upstream origin | |||||
remoteConfigHasNoURIAssociated=Remote config "{0}" has no URIs associated | remoteConfigHasNoURIAssociated=Remote config "{0}" has no URIs associated | ||||
remoteDoesNotHaveSpec=Remote does not have {0} available for fetch. | remoteDoesNotHaveSpec=Remote does not have {0} available for fetch. | ||||
remoteDoesNotSupportSmartHTTPPush=remote does not support smart HTTP push | remoteDoesNotSupportSmartHTTPPush=remote does not support smart HTTP push |
command.setTagOpt( | command.setTagOpt( | ||||
fetchAll ? TagOpt.FETCH_TAGS : TagOpt.AUTO_FOLLOW); | fetchAll ? TagOpt.FETCH_TAGS : TagOpt.AUTO_FOLLOW); | ||||
} | } | ||||
command.setInitialBranch(branch); | |||||
configure(command); | configure(command); | ||||
return command.call(); | return command.call(); |
private boolean isForceUpdate; | private boolean isForceUpdate; | ||||
private String initialBranch; | |||||
/** | /** | ||||
* Callback for status of fetch operation. | * Callback for status of fetch operation. | ||||
* | * | ||||
transport.setFetchThin(thin); | transport.setFetchThin(thin); | ||||
configure(transport); | configure(transport); | ||||
FetchResult result = transport.fetch(monitor, | FetchResult result = transport.fetch(monitor, | ||||
applyOptions(refSpecs)); | |||||
applyOptions(refSpecs), initialBranch); | |||||
if (!repo.isBare()) { | if (!repo.isBare()) { | ||||
fetchSubmodules(result); | fetchSubmodules(result); | ||||
} | } | ||||
return this; | 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. | * Register a progress callback. | ||||
* | * |
/***/ public String reftableDirExists; | /***/ public String reftableDirExists; | ||||
/***/ public String reftableRecordsMustIncrease; | /***/ public String reftableRecordsMustIncrease; | ||||
/***/ public String refUpdateReturnCodeWas; | /***/ public String refUpdateReturnCodeWas; | ||||
/***/ public String remoteBranchNotFound; | |||||
/***/ public String remoteConfigHasNoURIAssociated; | /***/ public String remoteConfigHasNoURIAssociated; | ||||
/***/ public String remoteDoesNotHaveSpec; | /***/ public String remoteDoesNotHaveSpec; | ||||
/***/ public String remoteDoesNotSupportSmartHTTPPush; | /***/ public String remoteDoesNotSupportSmartHTTPPush; |
import org.eclipse.jgit.lib.RefDatabase; | import org.eclipse.jgit.lib.RefDatabase; | ||||
import org.eclipse.jgit.revwalk.ObjectWalk; | import org.eclipse.jgit.revwalk.ObjectWalk; | ||||
import org.eclipse.jgit.revwalk.RevWalk; | import org.eclipse.jgit.revwalk.RevWalk; | ||||
import org.eclipse.jgit.util.StringUtils; | |||||
class FetchProcess { | class FetchProcess { | ||||
/** Transport we will fetch over. */ | /** Transport we will fetch over. */ | ||||
toFetch = f; | toFetch = f; | ||||
} | } | ||||
void execute(ProgressMonitor monitor, FetchResult result) | |||||
void execute(ProgressMonitor monitor, FetchResult result, | |||||
String initialBranch) | |||||
throws NotSupportedException, TransportException { | throws NotSupportedException, TransportException { | ||||
askFor.clear(); | askFor.clear(); | ||||
localUpdates.clear(); | localUpdates.clear(); | ||||
Throwable e1 = null; | Throwable e1 = null; | ||||
try { | try { | ||||
executeImp(monitor, result); | |||||
executeImp(monitor, result, initialBranch); | |||||
} catch (NotSupportedException | TransportException err) { | } catch (NotSupportedException | TransportException err) { | ||||
e1 = err; | e1 = err; | ||||
throw err; | throw err; | ||||
} | } | ||||
} | } | ||||
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, | 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(); | final TagOpt tagopt = transport.getTagOpt(); | ||||
String getTags = (tagopt == TagOpt.NO_TAGS) ? null : Constants.R_TAGS; | String getTags = (tagopt == TagOpt.NO_TAGS) ? null : Constants.R_TAGS; | ||||
String getHead = null; | String getHead = null; | ||||
} | } | ||||
conn = transport.openFetch(toFetch, getTags, getHead); | conn = transport.openFetch(toFetch, getTags, getHead); | ||||
try { | 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(); | result.peerUserAgent = conn.getPeerUserAgent(); | ||||
final Set<Ref> matched = new HashSet<>(); | final Set<Ref> matched = new HashSet<>(); | ||||
for (RefSpec spec : toFetch) { | for (RefSpec spec : toFetch) { |
* the remote connection could not be established or object | * the remote connection could not be established or object | ||||
* copying (if necessary) failed or update specification was | * copying (if necessary) failed or update specification was | ||||
* incorrect. | * 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, | public FetchResult fetch(final ProgressMonitor monitor, | ||||
Collection<RefSpec> toFetch) throws NotSupportedException, | |||||
Collection<RefSpec> toFetch, String branch) | |||||
throws NotSupportedException, | |||||
TransportException { | TransportException { | ||||
if (toFetch == null || toFetch.isEmpty()) { | if (toFetch == null || toFetch.isEmpty()) { | ||||
// If the caller did not ask for anything use the defaults. | // If the caller did not ask for anything use the defaults. | ||||
} | } | ||||
final FetchResult result = new FetchResult(); | final FetchResult result = new FetchResult(); | ||||
new FetchProcess(this, toFetch).execute(monitor, result); | |||||
new FetchProcess(this, toFetch).execute(monitor, result, branch); | |||||
local.autoGC(monitor); | local.autoGC(monitor); | ||||